mirror of
https://github.com/esphome/esphome.git
synced 2024-11-22 11:47:30 +01:00
Implementation for Atlas Scientific Peristaltic Pump (#3528)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
41b5cb06d3
commit
f30e54d177
@ -79,6 +79,7 @@ esphome/components/esp8266/* @esphome/core
|
||||
esphome/components/ethernet_info/* @gtjadsonsantos
|
||||
esphome/components/exposure_notifications/* @OttoWinter
|
||||
esphome/components/ezo/* @ssieb
|
||||
esphome/components/ezo_pmp/* @carlos-sarmiento
|
||||
esphome/components/factory_reset/* @anatoly-savchenkov
|
||||
esphome/components/fastled_base/* @OttoWinter
|
||||
esphome/components/feedback/* @ianchi
|
||||
|
291
esphome/components/ezo_pmp/__init__.py
Normal file
291
esphome/components/ezo_pmp/__init__.py
Normal file
@ -0,0 +1,291 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ADDRESS, CONF_COMMAND, CONF_ID, CONF_DURATION
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
|
||||
CODEOWNERS = ["@carlos-sarmiento"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_VOLUME = "volume"
|
||||
CONF_VOLUME_PER_MINUTE = "volume_per_minute"
|
||||
|
||||
ezo_pmp_ns = cg.esphome_ns.namespace("ezo_pmp")
|
||||
EzoPMP = ezo_pmp_ns.class_("EzoPMP", cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(EzoPMP),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(103))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
|
||||
EZO_PMP_NO_ARGS_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(EzoPMP),
|
||||
}
|
||||
)
|
||||
|
||||
# Actions that do not require more arguments
|
||||
|
||||
EzoPMPFindAction = ezo_pmp_ns.class_("EzoPMPFindAction", automation.Action)
|
||||
EzoPMPClearTotalVolumeDispensedAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPClearTotalVolumeDispensedAction", automation.Action
|
||||
)
|
||||
EzoPMPClearCalibrationAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPClearCalibrationAction", automation.Action
|
||||
)
|
||||
EzoPMPPauseDosingAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPPauseDosingAction", automation.Action
|
||||
)
|
||||
EzoPMPStopDosingAction = ezo_pmp_ns.class_("EzoPMPStopDosingAction", automation.Action)
|
||||
EzoPMPDoseContinuouslyAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPDoseContinuouslyAction", automation.Action
|
||||
)
|
||||
|
||||
# Actions that require more arguments
|
||||
EzoPMPDoseVolumeAction = ezo_pmp_ns.class_("EzoPMPDoseVolumeAction", automation.Action)
|
||||
EzoPMPDoseVolumeOverTimeAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPDoseVolumeOverTimeAction", automation.Action
|
||||
)
|
||||
EzoPMPDoseWithConstantFlowRateAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPDoseWithConstantFlowRateAction", automation.Action
|
||||
)
|
||||
EzoPMPSetCalibrationVolumeAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPSetCalibrationVolumeAction", automation.Action
|
||||
)
|
||||
EzoPMPChangeI2CAddressAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPChangeI2CAddressAction", automation.Action
|
||||
)
|
||||
EzoPMPArbitraryCommandAction = ezo_pmp_ns.class_(
|
||||
"EzoPMPArbitraryCommandAction", automation.Action
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ezo_pmp.find", EzoPMPFindAction, EZO_PMP_NO_ARGS_ACTION_SCHEMA
|
||||
)
|
||||
async def ezo_pmp_find_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ezo_pmp.dose_continuously",
|
||||
EzoPMPDoseContinuouslyAction,
|
||||
EZO_PMP_NO_ARGS_ACTION_SCHEMA,
|
||||
)
|
||||
async def ezo_pmp_dose_continuously_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ezo_pmp.clear_total_volume_dosed",
|
||||
EzoPMPClearTotalVolumeDispensedAction,
|
||||
EZO_PMP_NO_ARGS_ACTION_SCHEMA,
|
||||
)
|
||||
async def ezo_pmp_clear_total_volume_dosed_to_code(
|
||||
config, action_id, template_arg, args
|
||||
):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ezo_pmp.clear_calibration",
|
||||
EzoPMPClearCalibrationAction,
|
||||
EZO_PMP_NO_ARGS_ACTION_SCHEMA,
|
||||
)
|
||||
async def ezo_pmp_clear_calibration_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ezo_pmp.pause_dosing", EzoPMPPauseDosingAction, EZO_PMP_NO_ARGS_ACTION_SCHEMA
|
||||
)
|
||||
async def ezo_pmp_pause_dosing_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ezo_pmp.stop_dosing", EzoPMPStopDosingAction, EZO_PMP_NO_ARGS_ACTION_SCHEMA
|
||||
)
|
||||
async def ezo_pmp_stop_dosing_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
# Actions that require Multiple Args
|
||||
|
||||
EZO_PMP_DOSE_VOLUME_ACTION_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(EzoPMP),
|
||||
cv.Required(CONF_VOLUME): cv.templatable(
|
||||
cv.float_range()
|
||||
), # Any way to represent as proper volume (vs. raw int)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ezo_pmp.dose_volume", EzoPMPDoseVolumeAction, EZO_PMP_DOSE_VOLUME_ACTION_SCHEMA
|
||||
)
|
||||
async def ezo_pmp_dose_volume_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config[CONF_VOLUME], args, cg.double)
|
||||
cg.add(var.set_volume(template_))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
EZO_PMP_DOSE_VOLUME_OVER_TIME_ACTION_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(EzoPMP),
|
||||
cv.Required(CONF_VOLUME): cv.templatable(
|
||||
cv.float_range()
|
||||
), # Any way to represent as proper volume (vs. raw int)
|
||||
cv.Required(CONF_DURATION): cv.templatable(
|
||||
cv.int_range(1)
|
||||
), # Any way to represent it as minutes (vs. raw int)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ezo_pmp.dose_volume_over_time",
|
||||
EzoPMPDoseVolumeOverTimeAction,
|
||||
EZO_PMP_DOSE_VOLUME_OVER_TIME_ACTION_SCHEMA,
|
||||
)
|
||||
async def ezo_pmp_dose_volume_over_time_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config[CONF_VOLUME], args, cg.double)
|
||||
cg.add(var.set_volume(template_))
|
||||
|
||||
template_ = await cg.templatable(config[CONF_DURATION], args, int)
|
||||
cg.add(var.set_duration(template_))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
EZO_PMP_DOSE_WITH_CONSTANT_FLOW_RATE_ACTION_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(EzoPMP),
|
||||
cv.Required(CONF_VOLUME_PER_MINUTE): cv.templatable(
|
||||
cv.float_range()
|
||||
), # Any way to represent as proper volume (vs. raw int)
|
||||
cv.Required(CONF_DURATION): cv.templatable(
|
||||
cv.int_range(1)
|
||||
), # Any way to represent it as minutes (vs. raw int)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ezo_pmp.dose_with_constant_flow_rate",
|
||||
EzoPMPDoseWithConstantFlowRateAction,
|
||||
EZO_PMP_DOSE_WITH_CONSTANT_FLOW_RATE_ACTION_SCHEMA,
|
||||
)
|
||||
async def ezo_pmp_dose_with_constant_flow_rate_to_code(
|
||||
config, action_id, template_arg, args
|
||||
):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config[CONF_VOLUME_PER_MINUTE], args, cg.double)
|
||||
cg.add(var.set_volume(template_))
|
||||
|
||||
template_ = await cg.templatable(config[CONF_DURATION], args, int)
|
||||
cg.add(var.set_duration(template_))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
EZO_PMP_SET_CALIBRATION_VOLUME_ACTION_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(EzoPMP),
|
||||
cv.Required(CONF_VOLUME): cv.templatable(
|
||||
cv.float_range()
|
||||
), # Any way to represent as proper volume (vs. raw int)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ezo_pmp.set_calibration_volume",
|
||||
EzoPMPSetCalibrationVolumeAction,
|
||||
EZO_PMP_SET_CALIBRATION_VOLUME_ACTION_SCHEMA,
|
||||
)
|
||||
async def ezo_pmp_set_calibration_volume_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config[CONF_VOLUME], args, cg.double)
|
||||
cg.add(var.set_volume(template_))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
EZO_PMP_CHANGE_I2C_ADDRESS_ACTION_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(EzoPMP),
|
||||
cv.Required(CONF_ADDRESS): cv.templatable(cv.int_range(min=1, max=127)),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ezo_pmp.change_i2c_address",
|
||||
EzoPMPChangeI2CAddressAction,
|
||||
EZO_PMP_CHANGE_I2C_ADDRESS_ACTION_SCHEMA,
|
||||
)
|
||||
async def ezo_pmp_change_i2c_address_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.double)
|
||||
cg.add(var.set_address(template_))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
EZO_PMP_ARBITRARY_COMMAND_ACTION_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(EzoPMP),
|
||||
cv.Required(CONF_COMMAND): cv.templatable(cv.string_strict),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ezo_pmp.arbitrary_command",
|
||||
EzoPMPArbitraryCommandAction,
|
||||
EZO_PMP_ARBITRARY_COMMAND_ACTION_SCHEMA,
|
||||
)
|
||||
async def ezo_pmp_arbitrary_command_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.std_string)
|
||||
cg.add(var.set_command(template_))
|
||||
|
||||
return var
|
42
esphome/components/ezo_pmp/binary_sensor.py
Normal file
42
esphome/components/ezo_pmp/binary_sensor.py
Normal file
@ -0,0 +1,42 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import (
|
||||
ENTITY_CATEGORY_NONE,
|
||||
DEVICE_CLASS_RUNNING,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
CONF_ID,
|
||||
)
|
||||
|
||||
from . import EzoPMP
|
||||
|
||||
DEPENDENCIES = ["ezo_pmp"]
|
||||
|
||||
CONF_PUMP_STATE = "pump_state"
|
||||
CONF_IS_PAUSED = "is_paused"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(EzoPMP),
|
||||
cv.Optional(CONF_PUMP_STATE): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_RUNNING,
|
||||
entity_category=ENTITY_CATEGORY_NONE,
|
||||
),
|
||||
cv.Optional(CONF_IS_PAUSED): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
entity_category=ENTITY_CATEGORY_NONE,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
|
||||
if CONF_PUMP_STATE in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_PUMP_STATE])
|
||||
cg.add(parent.set_is_dosing(sens))
|
||||
|
||||
if CONF_IS_PAUSED in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_IS_PAUSED])
|
||||
cg.add(parent.set_is_paused(sens))
|
542
esphome/components/ezo_pmp/ezo_pmp.cpp
Normal file
542
esphome/components/ezo_pmp/ezo_pmp.cpp
Normal file
@ -0,0 +1,542 @@
|
||||
#include "ezo_pmp.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ezo_pmp {
|
||||
|
||||
static const char *const TAG = "ezo-pmp";
|
||||
|
||||
static const uint16_t EZO_PMP_COMMAND_NONE = 0;
|
||||
static const uint16_t EZO_PMP_COMMAND_TYPE_READ = 1;
|
||||
|
||||
static const uint16_t EZO_PMP_COMMAND_FIND = 2;
|
||||
static const uint16_t EZO_PMP_COMMAND_DOSE_CONTINUOUSLY = 4;
|
||||
static const uint16_t EZO_PMP_COMMAND_DOSE_VOLUME = 8;
|
||||
static const uint16_t EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME = 16;
|
||||
static const uint16_t EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE = 32;
|
||||
static const uint16_t EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME = 64;
|
||||
static const uint16_t EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED = 128;
|
||||
static const uint16_t EZO_PMP_COMMAND_CLEAR_CALIBRATION = 256;
|
||||
static const uint16_t EZO_PMP_COMMAND_PAUSE_DOSING = 512;
|
||||
static const uint16_t EZO_PMP_COMMAND_STOP_DOSING = 1024;
|
||||
static const uint16_t EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS = 2048;
|
||||
static const uint16_t EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS = 4096;
|
||||
|
||||
static const uint16_t EZO_PMP_COMMAND_READ_DOSING = 3;
|
||||
static const uint16_t EZO_PMP_COMMAND_READ_SINGLE_REPORT = 5;
|
||||
static const uint16_t EZO_PMP_COMMAND_READ_MAX_FLOW_RATE = 9;
|
||||
static const uint16_t EZO_PMP_COMMAND_READ_PAUSE_STATUS = 17;
|
||||
static const uint16_t EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED = 33;
|
||||
static const uint16_t EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED = 65;
|
||||
static const uint16_t EZO_PMP_COMMAND_READ_CALIBRATION_STATUS = 129;
|
||||
static const uint16_t EZO_PMP_COMMAND_READ_PUMP_VOLTAGE = 257;
|
||||
|
||||
static const std::string DOSING_MODE_NONE = "None";
|
||||
static const std::string DOSING_MODE_VOLUME = "Volume";
|
||||
static const std::string DOSING_MODE_VOLUME_OVER_TIME = "Volume/Time";
|
||||
static const std::string DOSING_MODE_CONSTANT_FLOW_RATE = "Constant Flow Rate";
|
||||
static const std::string DOSING_MODE_CONTINUOUS = "Continuous";
|
||||
|
||||
void EzoPMP::dump_config() {
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed())
|
||||
ESP_LOGE(TAG, "Communication with EZO-PMP circuit failed!");
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void EzoPMP::update() {
|
||||
if (this->is_waiting_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->is_first_read_) {
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_CALIBRATION_STATUS, 0, 0, (bool) this->calibration_status_);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_MAX_FLOW_RATE, 0, 0, (bool) this->max_flow_rate_);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED, 0, 0, (bool) this->total_volume_dosed_);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED, 0, 0,
|
||||
(bool) this->absolute_total_volume_dosed_);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_PAUSE_STATUS, 0, 0, true);
|
||||
this->is_first_read_ = false;
|
||||
}
|
||||
|
||||
if (!this->is_waiting_ && this->peek_next_command_() == EZO_PMP_COMMAND_NONE) {
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
|
||||
|
||||
if (this->is_dosing_flag_) {
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED, 0, 0, (bool) this->total_volume_dosed_);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED, 0, 0,
|
||||
(bool) this->absolute_total_volume_dosed_);
|
||||
}
|
||||
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_PUMP_VOLTAGE, 0, 0, (bool) this->pump_voltage_);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Not Scheduling new Command during update()");
|
||||
}
|
||||
}
|
||||
|
||||
void EzoPMP::loop() {
|
||||
// If we are not waiting for anything and there is no command to be sent, return
|
||||
if (!this->is_waiting_ && this->peek_next_command_() == EZO_PMP_COMMAND_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are not waiting for anything and there IS a command to be sent, do it.
|
||||
if (!this->is_waiting_ && this->peek_next_command_() != EZO_PMP_COMMAND_NONE) {
|
||||
this->send_next_command_();
|
||||
}
|
||||
|
||||
// If we are waiting for something but it isn't ready yet, then return
|
||||
if (this->is_waiting_ && millis() - this->start_time_ < this->wait_time_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We are waiting for something and it should be ready.
|
||||
this->read_command_result_();
|
||||
}
|
||||
|
||||
void EzoPMP::clear_current_command_() {
|
||||
this->current_command_ = EZO_PMP_COMMAND_NONE;
|
||||
this->is_waiting_ = false;
|
||||
}
|
||||
|
||||
void EzoPMP::read_command_result_() {
|
||||
uint8_t response_buffer[21] = {'\0'};
|
||||
|
||||
response_buffer[0] = 0;
|
||||
if (!this->read_bytes_raw(response_buffer, 20)) {
|
||||
ESP_LOGE(TAG, "read error");
|
||||
this->clear_current_command_();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (response_buffer[0]) {
|
||||
case 254:
|
||||
return; // keep waiting
|
||||
case 1:
|
||||
break;
|
||||
case 2:
|
||||
ESP_LOGE(TAG, "device returned a syntax error");
|
||||
this->clear_current_command_();
|
||||
return;
|
||||
case 255:
|
||||
ESP_LOGE(TAG, "device returned no data");
|
||||
this->clear_current_command_();
|
||||
return;
|
||||
default:
|
||||
ESP_LOGE(TAG, "device returned an unknown response: %d", response_buffer[0]);
|
||||
this->clear_current_command_();
|
||||
return;
|
||||
}
|
||||
|
||||
char first_parameter_buffer[10] = {'\0'};
|
||||
char second_parameter_buffer[10] = {'\0'};
|
||||
char third_parameter_buffer[10] = {'\0'};
|
||||
|
||||
first_parameter_buffer[0] = '\0';
|
||||
second_parameter_buffer[0] = '\0';
|
||||
third_parameter_buffer[0] = '\0';
|
||||
|
||||
int current_parameter = 1;
|
||||
|
||||
size_t position_in_parameter_buffer = 0;
|
||||
// some sensors return multiple comma-separated values, terminate string after first one
|
||||
for (size_t i = 1; i < sizeof(response_buffer) - 1; i++) {
|
||||
char current_char = response_buffer[i];
|
||||
|
||||
if (current_char == '\0') {
|
||||
ESP_LOGV(TAG, "Read Response from device: %s", (char *) response_buffer);
|
||||
ESP_LOGV(TAG, "First Component: %s", (char *) first_parameter_buffer);
|
||||
ESP_LOGV(TAG, "Second Component: %s", (char *) second_parameter_buffer);
|
||||
ESP_LOGV(TAG, "Third Component: %s", (char *) third_parameter_buffer);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (current_char == ',') {
|
||||
current_parameter++;
|
||||
position_in_parameter_buffer = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (current_parameter) {
|
||||
case 1:
|
||||
first_parameter_buffer[position_in_parameter_buffer] = current_char;
|
||||
first_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
|
||||
break;
|
||||
case 2:
|
||||
second_parameter_buffer[position_in_parameter_buffer] = current_char;
|
||||
second_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
|
||||
break;
|
||||
case 3:
|
||||
third_parameter_buffer[position_in_parameter_buffer] = current_char;
|
||||
third_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
|
||||
break;
|
||||
}
|
||||
|
||||
position_in_parameter_buffer++;
|
||||
}
|
||||
|
||||
auto parsed_first_parameter = parse_number<float>(first_parameter_buffer);
|
||||
auto parsed_second_parameter = parse_number<float>(second_parameter_buffer);
|
||||
auto parsed_third_parameter = parse_number<float>(third_parameter_buffer);
|
||||
|
||||
switch (this->current_command_) {
|
||||
// Read Commands
|
||||
case EZO_PMP_COMMAND_READ_DOSING: // Page 54
|
||||
if (parsed_third_parameter.has_value())
|
||||
this->is_dosing_flag_ = parsed_third_parameter.value_or(0) == 1;
|
||||
|
||||
if (this->is_dosing_)
|
||||
this->is_dosing_->publish_state(this->is_dosing_flag_);
|
||||
|
||||
if (parsed_second_parameter.has_value() && this->last_volume_requested_) {
|
||||
this->last_volume_requested_->publish_state(parsed_second_parameter.value_or(0));
|
||||
}
|
||||
|
||||
if (!this->is_dosing_flag_ && !this->is_paused_flag_) {
|
||||
// If pump is not paused and not dispensing
|
||||
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_NONE)
|
||||
this->dosing_mode_->publish_state(DOSING_MODE_NONE);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_SINGLE_REPORT: // Single Report (page 53)
|
||||
if (parsed_first_parameter.has_value() && (bool) this->current_volume_dosed_) {
|
||||
this->current_volume_dosed_->publish_state(parsed_first_parameter.value_or(0));
|
||||
}
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_MAX_FLOW_RATE: // Constant Flow Rate (page 57)
|
||||
if (parsed_second_parameter.has_value() && this->max_flow_rate_)
|
||||
this->max_flow_rate_->publish_state(parsed_second_parameter.value_or(0));
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_PAUSE_STATUS: // Pause (page 61)
|
||||
if (parsed_second_parameter.has_value())
|
||||
this->is_paused_flag_ = parsed_second_parameter.value_or(0) == 1;
|
||||
|
||||
if (this->is_paused_)
|
||||
this->is_paused_->publish_state(this->is_paused_flag_);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED: // Total Volume Dispensed (page 64)
|
||||
if (parsed_second_parameter.has_value() && this->total_volume_dosed_)
|
||||
this->total_volume_dosed_->publish_state(parsed_second_parameter.value_or(0));
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED: // Total Volume Dispensed (page 64)
|
||||
if (parsed_second_parameter.has_value() && this->absolute_total_volume_dosed_)
|
||||
this->absolute_total_volume_dosed_->publish_state(parsed_second_parameter.value_or(0));
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_CALIBRATION_STATUS: // Calibration (page 65)
|
||||
if (parsed_second_parameter.has_value() && this->calibration_status_) {
|
||||
if (parsed_second_parameter.value_or(0) == 1) {
|
||||
this->calibration_status_->publish_state("Fixed Volume");
|
||||
} else if (parsed_second_parameter.value_or(0) == 2) {
|
||||
this->calibration_status_->publish_state("Volume/Time");
|
||||
} else if (parsed_second_parameter.value_or(0) == 3) {
|
||||
this->calibration_status_->publish_state("Fixed Volume & Volume/Time");
|
||||
} else {
|
||||
this->calibration_status_->publish_state("Uncalibrated");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_PUMP_VOLTAGE: // Pump Voltage (page 67)
|
||||
if (parsed_second_parameter.has_value() && this->pump_voltage_)
|
||||
this->pump_voltage_->publish_state(parsed_second_parameter.value_or(0));
|
||||
break;
|
||||
|
||||
// Non-Read Commands
|
||||
|
||||
case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55)
|
||||
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_VOLUME)
|
||||
this->dosing_mode_->publish_state(DOSING_MODE_VOLUME);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56)
|
||||
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_VOLUME_OVER_TIME)
|
||||
this->dosing_mode_->publish_state(DOSING_MODE_VOLUME_OVER_TIME);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57)
|
||||
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_CONSTANT_FLOW_RATE)
|
||||
this->dosing_mode_->publish_state(DOSING_MODE_CONSTANT_FLOW_RATE);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54)
|
||||
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_CONTINUOUS)
|
||||
this->dosing_mode_->publish_state(DOSING_MODE_CONTINUOUS);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_STOP_DOSING: // Stop (page 62)
|
||||
this->is_paused_flag_ = false;
|
||||
if (this->is_paused_)
|
||||
this->is_paused_->publish_state(this->is_paused_flag_);
|
||||
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_NONE)
|
||||
this->dosing_mode_->publish_state(DOSING_MODE_NONE);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS:
|
||||
ESP_LOGI(TAG, "Arbitrary Command Response: %s", (char *) response_buffer);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_CLEAR_CALIBRATION: // Clear Calibration (page 65)
|
||||
case EZO_PMP_COMMAND_PAUSE_DOSING: // Pause (page 61)
|
||||
case EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME: // Set Calibration Volume (page 65)
|
||||
case EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED: // Clear Total Volume Dosed (page 64)
|
||||
case EZO_PMP_COMMAND_FIND: // Find (page 52)
|
||||
// Nothing to do here
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_TYPE_READ:
|
||||
case EZO_PMP_COMMAND_NONE:
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unsupported command received: %d", this->current_command_);
|
||||
return;
|
||||
}
|
||||
|
||||
this->clear_current_command_();
|
||||
}
|
||||
|
||||
void EzoPMP::send_next_command_() {
|
||||
int wait_time_for_command = 400; // milliseconds
|
||||
uint8_t command_buffer[21];
|
||||
int command_buffer_length = 0;
|
||||
|
||||
this->pop_next_command_(); // this->next_command will be updated.
|
||||
|
||||
switch (this->next_command_) {
|
||||
// Read Commands
|
||||
case EZO_PMP_COMMAND_READ_DOSING: // Page 54
|
||||
command_buffer_length = sprintf((char *) command_buffer, "D,?");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_SINGLE_REPORT: // Single Report (page 53)
|
||||
command_buffer_length = sprintf((char *) command_buffer, "R");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_MAX_FLOW_RATE:
|
||||
command_buffer_length = sprintf((char *) command_buffer, "DC,?");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_PAUSE_STATUS:
|
||||
command_buffer_length = sprintf((char *) command_buffer, "P,?");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED:
|
||||
command_buffer_length = sprintf((char *) command_buffer, "TV,?");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED:
|
||||
command_buffer_length = sprintf((char *) command_buffer, "ATV,?");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_CALIBRATION_STATUS:
|
||||
command_buffer_length = sprintf((char *) command_buffer, "Cal,?");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_READ_PUMP_VOLTAGE:
|
||||
command_buffer_length = sprintf((char *) command_buffer, "PV,?");
|
||||
break;
|
||||
|
||||
// Non-Read Commands
|
||||
|
||||
case EZO_PMP_COMMAND_FIND: // Find (page 52)
|
||||
command_buffer_length = sprintf((char *) command_buffer, "Find");
|
||||
wait_time_for_command = 60000; // This command will block all updates for a minute
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54)
|
||||
command_buffer_length = sprintf((char *) command_buffer, "D,*");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED: // Clear Total Volume Dosed (page 64)
|
||||
command_buffer_length = sprintf((char *) command_buffer, "Clear");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_CLEAR_CALIBRATION: // Clear Calibration (page 65)
|
||||
command_buffer_length = sprintf((char *) command_buffer, "Cal,clear");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_PAUSE_DOSING: // Pause (page 61)
|
||||
command_buffer_length = sprintf((char *) command_buffer, "P");
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_STOP_DOSING: // Stop (page 62)
|
||||
command_buffer_length = sprintf((char *) command_buffer, "X");
|
||||
break;
|
||||
|
||||
// Non-Read commands with parameters
|
||||
|
||||
case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55)
|
||||
command_buffer_length = sprintf((char *) command_buffer, "D,%0.1f", this->next_command_volume_);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56)
|
||||
command_buffer_length =
|
||||
sprintf((char *) command_buffer, "D,%0.1f,%i", this->next_command_volume_, this->next_command_duration_);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57)
|
||||
command_buffer_length =
|
||||
sprintf((char *) command_buffer, "DC,%0.1f,%i", this->next_command_volume_, this->next_command_duration_);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME: // Set Calibration Volume (page 65)
|
||||
command_buffer_length = sprintf((char *) command_buffer, "Cal,%0.2f", this->next_command_volume_);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS: // Change I2C Address (page 73)
|
||||
command_buffer_length = sprintf((char *) command_buffer, "I2C,%i", this->next_command_duration_);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS: // Run an arbitrary command
|
||||
command_buffer_length = sprintf((char *) command_buffer, this->arbitrary_command_, this->next_command_duration_);
|
||||
ESP_LOGI(TAG, "Sending arbitrary command: %s", (char *) command_buffer);
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_TYPE_READ:
|
||||
case EZO_PMP_COMMAND_NONE:
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unsupported command received: %d", this->next_command_);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send command
|
||||
ESP_LOGV(TAG, "Sending command to device: %s", (char *) command_buffer);
|
||||
this->write(command_buffer, command_buffer_length);
|
||||
|
||||
this->current_command_ = this->next_command_;
|
||||
this->next_command_ = EZO_PMP_COMMAND_NONE;
|
||||
this->is_waiting_ = true;
|
||||
this->start_time_ = millis();
|
||||
this->wait_time_ = wait_time_for_command;
|
||||
}
|
||||
|
||||
void EzoPMP::pop_next_command_() {
|
||||
if (this->next_command_queue_length_ <= 0) {
|
||||
ESP_LOGE(TAG, "Tried to dequeue command from empty queue");
|
||||
this->next_command_ = EZO_PMP_COMMAND_NONE;
|
||||
this->next_command_volume_ = 0;
|
||||
this->next_command_duration_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Read from Head
|
||||
this->next_command_ = this->next_command_queue_[this->next_command_queue_head_];
|
||||
this->next_command_volume_ = this->next_command_volume_queue_[this->next_command_queue_head_];
|
||||
this->next_command_duration_ = this->next_command_duration_queue_[this->next_command_queue_head_];
|
||||
|
||||
// Move positions
|
||||
next_command_queue_head_++;
|
||||
if (next_command_queue_head_ >= 10) {
|
||||
next_command_queue_head_ = 0;
|
||||
}
|
||||
|
||||
next_command_queue_length_--;
|
||||
}
|
||||
|
||||
uint16_t EzoPMP::peek_next_command_() {
|
||||
if (this->next_command_queue_length_ <= 0) {
|
||||
return EZO_PMP_COMMAND_NONE;
|
||||
}
|
||||
|
||||
return this->next_command_queue_[this->next_command_queue_head_];
|
||||
}
|
||||
|
||||
void EzoPMP::queue_command_(uint16_t command, double volume, int duration, bool should_schedule) {
|
||||
if (!should_schedule) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->next_command_queue_length_ >= 10) {
|
||||
ESP_LOGE(TAG, "Tried to queue command '%d' but queue is full", command);
|
||||
return;
|
||||
}
|
||||
|
||||
this->next_command_queue_[this->next_command_queue_last_] = command;
|
||||
this->next_command_volume_queue_[this->next_command_queue_last_] = volume;
|
||||
this->next_command_duration_queue_[this->next_command_queue_last_] = duration;
|
||||
|
||||
ESP_LOGV(TAG, "Queue command '%d' in position '%d'", command, next_command_queue_last_);
|
||||
|
||||
// Move positions
|
||||
next_command_queue_last_++;
|
||||
if (next_command_queue_last_ >= 10) {
|
||||
next_command_queue_last_ = 0;
|
||||
}
|
||||
|
||||
next_command_queue_length_++;
|
||||
}
|
||||
|
||||
// Actions
|
||||
|
||||
void EzoPMP::find() { this->queue_command_(EZO_PMP_COMMAND_FIND, 0, 0, true); }
|
||||
|
||||
void EzoPMP::dose_continuously() {
|
||||
this->queue_command_(EZO_PMP_COMMAND_DOSE_CONTINUOUSLY, 0, 0, true);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
|
||||
}
|
||||
|
||||
void EzoPMP::dose_volume(double volume) {
|
||||
this->queue_command_(EZO_PMP_COMMAND_DOSE_VOLUME, volume, 0, true);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
|
||||
}
|
||||
|
||||
void EzoPMP::dose_volume_over_time(double volume, int duration) {
|
||||
this->queue_command_(EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME, volume, duration, true);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
|
||||
}
|
||||
|
||||
void EzoPMP::dose_with_constant_flow_rate(double volume, int duration) {
|
||||
this->queue_command_(EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE, volume, duration, true);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
|
||||
}
|
||||
|
||||
void EzoPMP::set_calibration_volume(double volume) {
|
||||
this->queue_command_(EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME, volume, 0, true);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_CALIBRATION_STATUS, 0, 0, true);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_MAX_FLOW_RATE, 0, 0, true);
|
||||
}
|
||||
|
||||
void EzoPMP::clear_total_volume_dosed() {
|
||||
this->queue_command_(EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED, 0, 0, true);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, true);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED, 0, 0, true);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED, 0, 0, true);
|
||||
}
|
||||
|
||||
void EzoPMP::clear_calibration() {
|
||||
this->queue_command_(EZO_PMP_COMMAND_CLEAR_CALIBRATION, 0, 0, true);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_CALIBRATION_STATUS, 0, 0, true);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_MAX_FLOW_RATE, 0, 0, true);
|
||||
}
|
||||
|
||||
void EzoPMP::pause_dosing() {
|
||||
this->queue_command_(EZO_PMP_COMMAND_PAUSE_DOSING, 0, 0, true);
|
||||
this->queue_command_(EZO_PMP_COMMAND_READ_PAUSE_STATUS, 0, 0, true);
|
||||
}
|
||||
|
||||
void EzoPMP::stop_dosing() { this->queue_command_(EZO_PMP_COMMAND_STOP_DOSING, 0, 0, true); }
|
||||
|
||||
void EzoPMP::change_i2c_address(int address) {
|
||||
this->queue_command_(EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS, 0, address, true);
|
||||
}
|
||||
|
||||
void EzoPMP::exec_arbitrary_command(const std::basic_string<char> &command) {
|
||||
this->arbitrary_command_ = command.c_str();
|
||||
this->queue_command_(EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS, 0, 0, true);
|
||||
}
|
||||
|
||||
} // namespace ezo_pmp
|
||||
} // namespace esphome
|
252
esphome/components/ezo_pmp/ezo_pmp.h
Normal file
252
esphome/components/ezo_pmp/ezo_pmp.h
Normal file
@ -0,0 +1,252 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace ezo_pmp {
|
||||
|
||||
class EzoPMP : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; };
|
||||
|
||||
void loop() override;
|
||||
void update() override;
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void set_current_volume_dosed(sensor::Sensor *current_volume_dosed) { current_volume_dosed_ = current_volume_dosed; }
|
||||
void set_total_volume_dosed(sensor::Sensor *total_volume_dosed) { total_volume_dosed_ = total_volume_dosed; }
|
||||
void set_absolute_total_volume_dosed(sensor::Sensor *absolute_total_volume_dosed) {
|
||||
absolute_total_volume_dosed_ = absolute_total_volume_dosed;
|
||||
}
|
||||
void set_pump_voltage(sensor::Sensor *pump_voltage) { pump_voltage_ = pump_voltage; }
|
||||
void set_last_volume_requested(sensor::Sensor *last_volume_requested) {
|
||||
last_volume_requested_ = last_volume_requested;
|
||||
}
|
||||
void set_max_flow_rate(sensor::Sensor *max_flow_rate) { max_flow_rate_ = max_flow_rate; }
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void set_is_dosing(binary_sensor::BinarySensor *is_dosing) { is_dosing_ = is_dosing; }
|
||||
void set_is_paused(binary_sensor::BinarySensor *is_paused) { is_paused_ = is_paused; }
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void set_dosing_mode(text_sensor::TextSensor *dosing_mode) { dosing_mode_ = dosing_mode; }
|
||||
void set_calibration_status(text_sensor::TextSensor *calibration_status) { calibration_status_ = calibration_status; }
|
||||
#endif
|
||||
|
||||
// Actions for EZO-PMP
|
||||
void find();
|
||||
void dose_continuously();
|
||||
void dose_volume(double volume);
|
||||
void dose_volume_over_time(double volume, int duration);
|
||||
void dose_with_constant_flow_rate(double volume, int duration);
|
||||
void set_calibration_volume(double volume);
|
||||
void clear_total_volume_dosed();
|
||||
void clear_calibration();
|
||||
void pause_dosing();
|
||||
void stop_dosing();
|
||||
void change_i2c_address(int address);
|
||||
void exec_arbitrary_command(const std::basic_string<char> &command);
|
||||
|
||||
protected:
|
||||
uint32_t start_time_ = 0;
|
||||
uint32_t wait_time_ = 0;
|
||||
bool is_waiting_ = false;
|
||||
bool is_first_read_ = true;
|
||||
|
||||
uint16_t next_command_ = 0;
|
||||
double next_command_volume_ = 0; // might be negative
|
||||
int next_command_duration_ = 0;
|
||||
|
||||
uint16_t next_command_queue_[10];
|
||||
double next_command_volume_queue_[10];
|
||||
int next_command_duration_queue_[10];
|
||||
int next_command_queue_head_ = 0;
|
||||
int next_command_queue_last_ = 0;
|
||||
int next_command_queue_length_ = 0;
|
||||
|
||||
uint16_t current_command_ = 0;
|
||||
bool is_paused_flag_ = false;
|
||||
bool is_dosing_flag_ = false;
|
||||
|
||||
const char *arbitrary_command_{nullptr};
|
||||
|
||||
void send_next_command_();
|
||||
void read_command_result_();
|
||||
void clear_current_command_();
|
||||
void queue_command_(uint16_t command, double volume, int duration, bool should_schedule);
|
||||
void pop_next_command_();
|
||||
uint16_t peek_next_command_();
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
sensor::Sensor *current_volume_dosed_{nullptr};
|
||||
sensor::Sensor *total_volume_dosed_{nullptr};
|
||||
sensor::Sensor *absolute_total_volume_dosed_{nullptr};
|
||||
sensor::Sensor *pump_voltage_{nullptr};
|
||||
sensor::Sensor *max_flow_rate_{nullptr};
|
||||
sensor::Sensor *last_volume_requested_{nullptr};
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
binary_sensor::BinarySensor *is_dosing_{nullptr};
|
||||
binary_sensor::BinarySensor *is_paused_{nullptr};
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
text_sensor::TextSensor *dosing_mode_{nullptr};
|
||||
text_sensor::TextSensor *calibration_status_{nullptr};
|
||||
#endif
|
||||
};
|
||||
|
||||
// Action Templates
|
||||
template<typename... Ts> class EzoPMPFindAction : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPFindAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(Ts... x) override { this->ezopmp_->find(); }
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPDoseContinuouslyAction : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPDoseContinuouslyAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(Ts... x) override { this->ezopmp_->dose_continuously(); }
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPDoseVolumeAction : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPDoseVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(Ts... x) override { this->ezopmp_->dose_volume(this->volume_.value(x...)); }
|
||||
TEMPLATABLE_VALUE(double, volume)
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPDoseVolumeOverTimeAction : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPDoseVolumeOverTimeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->ezopmp_->dose_volume_over_time(this->volume_.value(x...), this->duration_.value(x...));
|
||||
}
|
||||
TEMPLATABLE_VALUE(double, volume)
|
||||
TEMPLATABLE_VALUE(int, duration)
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPDoseWithConstantFlowRateAction : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPDoseWithConstantFlowRateAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->ezopmp_->dose_with_constant_flow_rate(this->volume_.value(x...), this->duration_.value(x...));
|
||||
}
|
||||
TEMPLATABLE_VALUE(double, volume)
|
||||
TEMPLATABLE_VALUE(int, duration)
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPSetCalibrationVolumeAction : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPSetCalibrationVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(Ts... x) override { this->ezopmp_->set_calibration_volume(this->volume_.value(x...)); }
|
||||
TEMPLATABLE_VALUE(double, volume)
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPClearTotalVolumeDispensedAction : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPClearTotalVolumeDispensedAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(Ts... x) override { this->ezopmp_->clear_total_volume_dosed(); }
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPClearCalibrationAction : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPClearCalibrationAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(Ts... x) override { this->ezopmp_->clear_calibration(); }
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPPauseDosingAction : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPPauseDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(Ts... x) override { this->ezopmp_->pause_dosing(); }
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPStopDosingAction : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPStopDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(Ts... x) override { this->ezopmp_->stop_dosing(); }
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPChangeI2CAddressAction : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPChangeI2CAddressAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(Ts... x) override { this->ezopmp_->change_i2c_address(this->address_.value(x...)); }
|
||||
TEMPLATABLE_VALUE(int, address)
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class EzoPMPArbitraryCommandAction : public Action<Ts...> {
|
||||
public:
|
||||
EzoPMPArbitraryCommandAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
|
||||
|
||||
void play(Ts... x) override { this->ezopmp_->exec_arbitrary_command(this->command_.value(x...)); }
|
||||
TEMPLATABLE_VALUE(std::string, command)
|
||||
|
||||
protected:
|
||||
EzoPMP *ezopmp_;
|
||||
};
|
||||
|
||||
} // namespace ezo_pmp
|
||||
} // namespace esphome
|
104
esphome/components/ezo_pmp/sensor.py
Normal file
104
esphome/components/ezo_pmp/sensor.py
Normal file
@ -0,0 +1,104 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ENTITY_CATEGORY_NONE,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_NONE,
|
||||
CONF_ID,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
|
||||
from . import EzoPMP
|
||||
|
||||
|
||||
DEPENDENCIES = ["ezo_pmp"]
|
||||
|
||||
CONF_CURRENT_VOLUME_DOSED = "current_volume_dosed"
|
||||
CONF_TOTAL_VOLUME_DOSED = "total_volume_dosed"
|
||||
CONF_ABSOLUTE_TOTAL_VOLUME_DOSED = "absolute_total_volume_dosed"
|
||||
CONF_PUMP_VOLTAGE = "pump_voltage"
|
||||
CONF_LAST_VOLUME_REQUESTED = "last_volume_requested"
|
||||
CONF_MAX_FLOW_RATE = "max_flow_rate"
|
||||
|
||||
UNIT_MILILITER = "ml"
|
||||
UNIT_MILILITERS_PER_MINUTE = "ml/min"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(EzoPMP),
|
||||
cv.Optional(CONF_CURRENT_VOLUME_DOSED): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILILITER,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_NONE,
|
||||
),
|
||||
cv.Optional(CONF_LAST_VOLUME_REQUESTED): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILILITER,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_NONE,
|
||||
),
|
||||
cv.Optional(CONF_MAX_FLOW_RATE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILILITERS_PER_MINUTE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_NONE,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_TOTAL_VOLUME_DOSED): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILILITER,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_ABSOLUTE_TOTAL_VOLUME_DOSED): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILILITER,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_PUMP_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
|
||||
if CONF_CURRENT_VOLUME_DOSED in config:
|
||||
sens = await sensor.new_sensor(config[CONF_CURRENT_VOLUME_DOSED])
|
||||
cg.add(parent.set_current_volume_dosed(sens))
|
||||
|
||||
if CONF_LAST_VOLUME_REQUESTED in config:
|
||||
sens = await sensor.new_sensor(config[CONF_LAST_VOLUME_REQUESTED])
|
||||
cg.add(parent.set_last_volume_requested(sens))
|
||||
|
||||
if CONF_TOTAL_VOLUME_DOSED in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TOTAL_VOLUME_DOSED])
|
||||
cg.add(parent.set_total_volume_dosed(sens))
|
||||
|
||||
if CONF_ABSOLUTE_TOTAL_VOLUME_DOSED in config:
|
||||
sens = await sensor.new_sensor(config[CONF_ABSOLUTE_TOTAL_VOLUME_DOSED])
|
||||
cg.add(parent.set_absolute_total_volume_dosed(sens))
|
||||
|
||||
if CONF_PUMP_VOLTAGE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PUMP_VOLTAGE])
|
||||
cg.add(parent.set_pump_voltage(sens))
|
||||
|
||||
if CONF_MAX_FLOW_RATE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_MAX_FLOW_RATE])
|
||||
cg.add(parent.set_max_flow_rate(sens))
|
39
esphome/components/ezo_pmp/text_sensor.py
Normal file
39
esphome/components/ezo_pmp/text_sensor.py
Normal file
@ -0,0 +1,39 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import (
|
||||
ENTITY_CATEGORY_NONE,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
CONF_ID,
|
||||
)
|
||||
|
||||
from . import EzoPMP
|
||||
|
||||
DEPENDENCIES = ["ezo_pmp"]
|
||||
|
||||
CONF_DOSING_MODE = "dosing_mode"
|
||||
CONF_CALIBRATION_STATUS = "calibration_status"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(EzoPMP),
|
||||
cv.Optional(CONF_DOSING_MODE): text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_NONE,
|
||||
),
|
||||
cv.Optional(CONF_CALIBRATION_STATUS): text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
|
||||
if CONF_DOSING_MODE in config:
|
||||
sens = await text_sensor.new_text_sensor(config[CONF_DOSING_MODE])
|
||||
cg.add(parent.set_dosing_mode(sens))
|
||||
|
||||
if CONF_CALIBRATION_STATUS in config:
|
||||
sens = await text_sensor.new_text_sensor(config[CONF_CALIBRATION_STATUS])
|
||||
cg.add(parent.set_calibration_status(sens))
|
@ -165,6 +165,12 @@ binary_sensor:
|
||||
|
||||
|
||||
|
||||
- platform: ezo_pmp
|
||||
pump_state:
|
||||
name: "Pump State"
|
||||
is_paused:
|
||||
name: "Is Paused"
|
||||
|
||||
tlc5947:
|
||||
data_pin: GPIO12
|
||||
clock_pin: GPIO14
|
||||
@ -220,6 +226,10 @@ esp32_improv:
|
||||
authorized_duration: 1min
|
||||
status_indicator: built_in_led
|
||||
|
||||
ezo_pmp:
|
||||
id: hcl_pump
|
||||
update_interval: 1s
|
||||
|
||||
number:
|
||||
- platform: template
|
||||
name: My template number
|
||||
@ -440,6 +450,20 @@ sensor:
|
||||
cold_junction:
|
||||
name: Ambient Temperature
|
||||
|
||||
- platform: ezo_pmp
|
||||
current_volume_dosed:
|
||||
name: Current Volume Dosed
|
||||
total_volume_dosed:
|
||||
name: Total Volume Dosed
|
||||
absolute_total_volume_dosed:
|
||||
name: Absolute Total Volume Dosed
|
||||
pump_voltage:
|
||||
name: Pump Voltage
|
||||
last_volume_requested:
|
||||
name: Last Volume Requested
|
||||
max_flow_rate:
|
||||
name: Max Flow Rate
|
||||
|
||||
script:
|
||||
- id: automation_test
|
||||
then:
|
||||
@ -487,3 +511,33 @@ display:
|
||||
lambda: |-
|
||||
it.print("81818181");
|
||||
|
||||
text_sensor:
|
||||
- platform: ezo_pmp
|
||||
dosing_mode:
|
||||
name: Dosing Mode
|
||||
calibration_status:
|
||||
name: Calibration Status
|
||||
on_value:
|
||||
- ezo_pmp.dose_volume:
|
||||
id: hcl_pump
|
||||
volume: 10
|
||||
- ezo_pmp.dose_volume_over_time:
|
||||
id: hcl_pump
|
||||
volume: 10
|
||||
duration: 2
|
||||
- ezo_pmp.dose_with_constant_flow_rate:
|
||||
id: hcl_pump
|
||||
volume_per_minute: 10
|
||||
duration: 2
|
||||
- ezo_pmp.set_calibration_volume:
|
||||
id: hcl_pump
|
||||
volume: 10
|
||||
- ezo_pmp.find: hcl_pump
|
||||
- ezo_pmp.dose_continuously: hcl_pump
|
||||
- ezo_pmp.clear_total_volume_dosed: hcl_pump
|
||||
- ezo_pmp.clear_calibration: hcl_pump
|
||||
- ezo_pmp.pause_dosing: hcl_pump
|
||||
- ezo_pmp.stop_dosing: hcl_pump
|
||||
- ezo_pmp.arbitrary_command:
|
||||
id: hcl_pump
|
||||
command: D,?
|
||||
|
Loading…
Reference in New Issue
Block a user