mirror of
https://github.com/Threnklyn/esphome-dev.git
synced 2026-05-23 22:28:28 +02:00
Add Select for modbus (#3032)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import select
|
||||
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA
|
||||
from esphome.jsonschema import jschema_composite
|
||||
|
||||
from .. import (
|
||||
SENSOR_VALUE_TYPE,
|
||||
TYPE_REGISTER_MAP,
|
||||
ModbusController,
|
||||
SensorItem,
|
||||
modbus_controller_ns,
|
||||
)
|
||||
from ..const import (
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_REGISTER_COUNT,
|
||||
CONF_SKIP_UPDATES,
|
||||
CONF_USE_WRITE_MULTIPLE,
|
||||
CONF_VALUE_TYPE,
|
||||
CONF_WRITE_LAMBDA,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["modbus_controller"]
|
||||
CODEOWNERS = ["@martgras", "@stegm"]
|
||||
CONF_OPTIONSMAP = "optionsmap"
|
||||
|
||||
ModbusSelect = modbus_controller_ns.class_(
|
||||
"ModbusSelect", cg.Component, select.Select, SensorItem
|
||||
)
|
||||
|
||||
|
||||
@jschema_composite
|
||||
def ensure_option_map():
|
||||
def validator(value):
|
||||
cv.check_not_templatable(value)
|
||||
option = cv.All(cv.string_strict)
|
||||
mapping = cv.All(cv.int_range(-(2 ** 63), 2 ** 63 - 1))
|
||||
options_map_schema = cv.Schema({option: mapping})
|
||||
value = options_map_schema(value)
|
||||
|
||||
all_values = list(value.values())
|
||||
unique_values = set(value.values())
|
||||
if len(all_values) != len(unique_values):
|
||||
raise cv.Invalid("Mapping values must be unique.")
|
||||
|
||||
return value
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
def register_count_value_type_min(value):
|
||||
reg_count = value.get(CONF_REGISTER_COUNT)
|
||||
if reg_count is not None:
|
||||
value_type = value[CONF_VALUE_TYPE]
|
||||
min_register_count = TYPE_REGISTER_MAP[value_type]
|
||||
if min_register_count > reg_count:
|
||||
raise cv.Invalid(
|
||||
f"Value type {value_type} needs at least {min_register_count} registers"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
INTEGER_SENSOR_VALUE_TYPE = {
|
||||
key: value for key, value in SENSOR_VALUE_TYPE.items() if not key.startswith("FP")
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
select.SELECT_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ModbusSelect),
|
||||
cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
|
||||
cv.Required(CONF_ADDRESS): cv.positive_int,
|
||||
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(
|
||||
INTEGER_SENSOR_VALUE_TYPE
|
||||
),
|
||||
cv.Optional(CONF_REGISTER_COUNT): cv.positive_int,
|
||||
cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
|
||||
cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
|
||||
cv.Required(CONF_OPTIONSMAP): ensure_option_map(),
|
||||
cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
||||
},
|
||||
),
|
||||
register_count_value_type_min,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
value_type = config[CONF_VALUE_TYPE]
|
||||
reg_count = config.get(CONF_REGISTER_COUNT)
|
||||
if reg_count is None:
|
||||
reg_count = TYPE_REGISTER_MAP[value_type]
|
||||
|
||||
options_map = config[CONF_OPTIONSMAP]
|
||||
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
value_type,
|
||||
config[CONF_ADDRESS],
|
||||
reg_count,
|
||||
config[CONF_SKIP_UPDATES],
|
||||
config[CONF_FORCE_NEW_RANGE],
|
||||
list(options_map.values()),
|
||||
)
|
||||
|
||||
await cg.register_component(var, config)
|
||||
await select.register_select(var, config, options=list(options_map.keys()))
|
||||
|
||||
parent = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
|
||||
cg.add(parent.add_sensor_item(var))
|
||||
cg.add(var.set_parent(parent))
|
||||
cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE]))
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
config[CONF_LAMBDA],
|
||||
[
|
||||
(ModbusSelect.operator("const_ptr"), "item"),
|
||||
(cg.int64, "x"),
|
||||
(
|
||||
cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
|
||||
"data",
|
||||
),
|
||||
],
|
||||
return_type=cg.optional.template(cg.std_string),
|
||||
)
|
||||
cg.add(var.set_template(template_))
|
||||
|
||||
if CONF_WRITE_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
config[CONF_WRITE_LAMBDA],
|
||||
[
|
||||
(ModbusSelect.operator("const_ptr"), "item"),
|
||||
(cg.std_string.operator("const").operator("ref"), "x"),
|
||||
(cg.int64, "value"),
|
||||
(cg.std_vector.template(cg.uint16).operator("ref"), "payload"),
|
||||
],
|
||||
return_type=cg.optional.template(cg.int64),
|
||||
)
|
||||
cg.add(var.set_write_template(template_))
|
||||
@@ -0,0 +1,86 @@
|
||||
#include "modbus_select.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace modbus_controller {
|
||||
|
||||
static const char *const TAG = "modbus_controller.select";
|
||||
|
||||
void ModbusSelect::dump_config() { LOG_SELECT(TAG, "Modbus Controller Select", this); }
|
||||
|
||||
void ModbusSelect::parse_and_publish(const std::vector<uint8_t> &data) {
|
||||
int64_t value = payload_to_number(data, this->sensor_value_type, this->offset, this->bitmask);
|
||||
|
||||
ESP_LOGD(TAG, "New select value %lld from payload", value);
|
||||
|
||||
optional<std::string> new_state;
|
||||
|
||||
if (this->transform_func_.has_value()) {
|
||||
auto val = (*this->transform_func_)(this, value, data);
|
||||
if (val.has_value()) {
|
||||
new_state = *val;
|
||||
ESP_LOGV(TAG, "lambda returned option %s", new_state->c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (!new_state.has_value()) {
|
||||
auto map_it = std::find(this->mapping_.cbegin(), this->mapping_.cend(), value);
|
||||
|
||||
if (map_it != this->mapping_.cend()) {
|
||||
size_t idx = std::distance(this->mapping_.cbegin(), map_it);
|
||||
new_state = this->traits.get_options()[idx];
|
||||
ESP_LOGV(TAG, "Found option %s for value %lld", new_state->c_str(), value);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "No option found for mapping %lld", value);
|
||||
}
|
||||
}
|
||||
|
||||
if (new_state.has_value()) {
|
||||
this->publish_state(new_state.value());
|
||||
}
|
||||
}
|
||||
|
||||
void ModbusSelect::control(const std::string &value) {
|
||||
auto options = this->traits.get_options();
|
||||
auto opt_it = std::find(options.cbegin(), options.cend(), value);
|
||||
size_t idx = std::distance(options.cbegin(), opt_it);
|
||||
optional<int64_t> mapval = this->mapping_[idx];
|
||||
ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, value.c_str());
|
||||
|
||||
std::vector<uint16_t> data;
|
||||
|
||||
if (this->write_transform_func_.has_value()) {
|
||||
auto val = (*this->write_transform_func_)(this, value, *mapval, data);
|
||||
if (val.has_value()) {
|
||||
mapval = *val;
|
||||
ESP_LOGV(TAG, "write_lambda returned mapping value %lld", *mapval);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Communication handled by write_lambda - exiting control");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.empty()) {
|
||||
number_to_payload(data, *mapval, this->sensor_value_type);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Using payload from write lambda");
|
||||
}
|
||||
|
||||
if (data.empty()) {
|
||||
ESP_LOGW(TAG, "No payload was created for updating select");
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t write_address = this->start_address + this->offset / 2;
|
||||
ModbusCommandItem write_cmd;
|
||||
if ((this->register_count == 1) && (!this->use_write_multiple_)) {
|
||||
write_cmd = ModbusCommandItem::create_write_single_command(parent_, write_address, data[0]);
|
||||
} else {
|
||||
write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, write_address, this->register_count, data);
|
||||
}
|
||||
|
||||
parent_->queue_command(write_cmd);
|
||||
}
|
||||
|
||||
} // namespace modbus_controller
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "esphome/components/modbus_controller/modbus_controller.h"
|
||||
#include "esphome/components/select/select.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace modbus_controller {
|
||||
|
||||
class ModbusSelect : public Component, public select::Select, public SensorItem {
|
||||
public:
|
||||
ModbusSelect(SensorValueType sensor_value_type, uint16_t start_address, uint8_t register_count, uint8_t skip_updates,
|
||||
bool force_new_range, std::vector<int64_t> mapping) {
|
||||
this->register_type = ModbusRegisterType::HOLDING; // not configurable
|
||||
this->sensor_value_type = sensor_value_type;
|
||||
this->start_address = start_address;
|
||||
this->offset = 0; // not configurable
|
||||
this->bitmask = 0xFFFFFFFF; // not configurable
|
||||
this->register_count = register_count;
|
||||
this->response_bytes = 0; // not configurable
|
||||
this->skip_updates = skip_updates;
|
||||
this->force_new_range = force_new_range;
|
||||
this->mapping_ = std::move(mapping);
|
||||
}
|
||||
|
||||
using transform_func_t =
|
||||
std::function<optional<std::string>(ModbusSelect *const, int64_t, const std::vector<uint8_t> &)>;
|
||||
using write_transform_func_t =
|
||||
std::function<optional<int64_t>(ModbusSelect *const, const std::string &, int64_t, std::vector<uint16_t> &)>;
|
||||
|
||||
void set_parent(ModbusController *const parent) { this->parent_ = parent; }
|
||||
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
|
||||
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
|
||||
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
|
||||
|
||||
void dump_config() override;
|
||||
void parse_and_publish(const std::vector<uint8_t> &data) override;
|
||||
void control(const std::string &value) override;
|
||||
|
||||
protected:
|
||||
std::vector<int64_t> mapping_;
|
||||
ModbusController *parent_;
|
||||
bool use_write_multiple_{false};
|
||||
optional<transform_func_t> transform_func_;
|
||||
optional<write_transform_func_t> write_transform_func_;
|
||||
};
|
||||
|
||||
} // namespace modbus_controller
|
||||
} // namespace esphome
|
||||
Reference in New Issue
Block a user