mirror of
https://github.com/esphome/esphome.git
synced 2026-10-06 10:56:43 +00:00
[modbus_controller] Rename custom_command to custom_pdu; reject address 0; retire skip_updates (#18652)
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import binascii
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
@@ -10,7 +12,9 @@ from esphome.components.modbus.helpers import (
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_NAME, CONF_OFFSET
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_helpers import logging
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .const import (
|
||||
@@ -19,6 +23,7 @@ from .const import (
|
||||
CONF_BYTE_OFFSET,
|
||||
CONF_COMMAND_THROTTLE,
|
||||
CONF_CUSTOM_COMMAND,
|
||||
CONF_CUSTOM_PDU,
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MAX_CMD_RETRIES,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
@@ -41,6 +46,8 @@ AUTO_LOAD = ["modbus"]
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
DOMAIN = "modbus_controller"
|
||||
|
||||
modbus_controller_ns = cg.esphome_ns.namespace("modbus_controller")
|
||||
ModbusController = modbus_controller_ns.class_("ModbusController", cg.PollingComponent)
|
||||
|
||||
@@ -48,6 +55,19 @@ SensorItem = modbus_controller_ns.struct("SensorItem")
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ModbusControllerData:
|
||||
# Set once the deprecated 'skip_updates' warning has been emitted so we warn only once total.
|
||||
skip_updates_warned: bool = False
|
||||
|
||||
|
||||
def _get_data() -> ModbusControllerData:
|
||||
if DOMAIN not in CORE.data:
|
||||
CORE.data[DOMAIN] = ModbusControllerData()
|
||||
return CORE.data[DOMAIN]
|
||||
|
||||
|
||||
# Remove before 2027.2.0
|
||||
_REMOVED_OPTIONS = {
|
||||
CONF_COMMAND_THROTTLE: "Command spacing is handled by the 'modbus' component - use 'turnaround_time' there instead.",
|
||||
@@ -67,6 +87,32 @@ def _warn_removed_options(config: ConfigType) -> ConfigType:
|
||||
return config
|
||||
|
||||
|
||||
def _reject_broadcast_address(config: ConfigType) -> ConfigType:
|
||||
"""A modbus_controller polls one device, so its address cannot be the broadcast address (0):
|
||||
a broadcast is never answered (Modbus 4.1), so no register could ever read back."""
|
||||
modbus.reject_broadcast_address(
|
||||
config.get(CONF_ADDRESS),
|
||||
"a modbus_controller device address",
|
||||
"Assign the unit address of the device you want to poll.",
|
||||
[CONF_ADDRESS],
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
# Remove before 2027.3.0. skip_updates (a per-sensor option) no longer does anything: every range is
|
||||
# polled each update_interval. The key is still accepted so existing configs keep working, with a warning.
|
||||
def validate_skip_updates_deprecated(value: Any) -> int:
|
||||
data = _get_data()
|
||||
if not data.skip_updates_warned:
|
||||
_LOGGER.warning(
|
||||
"[modbus_controller] 'skip_updates' no longer has any effect and will be removed in 2027.3.0. "
|
||||
"To poll some registers less often, add a second modbus_controller with the same address and a "
|
||||
"slower update_interval, and attach the slow sensors to it."
|
||||
)
|
||||
data.skip_updates_warned = True
|
||||
return cv.positive_int(value)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -92,13 +138,32 @@ CONFIG_SCHEMA = cv.All(
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(modbus.modbus_device_schema(0x01)),
|
||||
_warn_removed_options,
|
||||
_reject_broadcast_address,
|
||||
)
|
||||
|
||||
ModbusItemBaseSchema = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
|
||||
cv.Optional(CONF_ADDRESS): cv.positive_int,
|
||||
cv.Optional(CONF_CUSTOM_COMMAND): cv.ensure_list(cv.hex_uint8_t),
|
||||
cv.Exclusive(
|
||||
CONF_CUSTOM_PDU,
|
||||
"custom_source",
|
||||
f"{CONF_CUSTOM_PDU} and {CONF_CUSTOM_COMMAND} can't be used together",
|
||||
): cv.All(
|
||||
cv.ensure_list(cv.hex_uint8_t),
|
||||
cv.Length(min=1, max=modbus.MAX_PDU_SIZE),
|
||||
),
|
||||
# Deprecated: takes a raw frame with a leading device address byte. Auto-migrated to
|
||||
# custom_pdu in migrate_custom_command (final validate). Remove before 2027.3.0. The upper
|
||||
# bound is MAX_PDU_SIZE + 1: the extra byte is the address the migration strips.
|
||||
cv.Exclusive(
|
||||
CONF_CUSTOM_COMMAND,
|
||||
"custom_source",
|
||||
f"{CONF_CUSTOM_PDU} and {CONF_CUSTOM_COMMAND} can't be used together",
|
||||
): cv.All(
|
||||
cv.ensure_list(cv.hex_uint8_t),
|
||||
cv.Length(min=2, max=modbus.MAX_PDU_SIZE + 1),
|
||||
),
|
||||
cv.Exclusive(
|
||||
CONF_OFFSET,
|
||||
"offset",
|
||||
@@ -110,7 +175,7 @@ ModbusItemBaseSchema = cv.Schema(
|
||||
f"{CONF_OFFSET} and {CONF_BYTE_OFFSET} can't be used together",
|
||||
): cv.positive_int,
|
||||
cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t,
|
||||
cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
|
||||
cv.Optional(CONF_SKIP_UPDATES): validate_skip_updates_deprecated,
|
||||
cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_RESPONSE_SIZE, default=0): cv.positive_int,
|
||||
@@ -119,22 +184,56 @@ ModbusItemBaseSchema = cv.Schema(
|
||||
|
||||
|
||||
def validate_modbus_register(config):
|
||||
if CONF_CUSTOM_COMMAND not in config and CONF_ADDRESS not in config:
|
||||
# custom_command is the deprecated alias for custom_pdu (migrated later in final validate); treat
|
||||
# either as "a custom frame is configured" so the address/register_type rules match.
|
||||
has_custom = CONF_CUSTOM_PDU in config or CONF_CUSTOM_COMMAND in config
|
||||
if not has_custom and CONF_ADDRESS not in config:
|
||||
raise cv.Invalid(
|
||||
f" {CONF_ADDRESS} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used"
|
||||
f" {CONF_ADDRESS} is a required property if '{CONF_CUSTOM_PDU}:' isn't used"
|
||||
)
|
||||
if CONF_CUSTOM_COMMAND in config and CONF_REGISTER_TYPE in config:
|
||||
if has_custom and CONF_REGISTER_TYPE in config:
|
||||
raise cv.Invalid(
|
||||
f"can't use '{CONF_REGISTER_TYPE}:' together with '{CONF_CUSTOM_COMMAND}:'",
|
||||
f"can't use '{CONF_REGISTER_TYPE}:' together with '{CONF_CUSTOM_PDU}:'",
|
||||
)
|
||||
|
||||
if CONF_CUSTOM_COMMAND not in config and CONF_REGISTER_TYPE not in config:
|
||||
if not has_custom and CONF_REGISTER_TYPE not in config:
|
||||
raise cv.Invalid(
|
||||
f" {CONF_REGISTER_TYPE} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used"
|
||||
f" {CONF_REGISTER_TYPE} is a required property if '{CONF_CUSTOM_PDU}:' isn't used"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def migrate_custom_command(config: ConfigType) -> None:
|
||||
"""Final-validate: auto-migrate the deprecated custom_command (raw frame incl. device address)
|
||||
to custom_pdu (PDU only). custom_pdu is always sent to the controller's own address, so a frame
|
||||
whose address byte does not match the controller's address is a hard error (it targeted a
|
||||
different unit). Mutates config in place; final validate discards the return value."""
|
||||
frame = config.get(CONF_CUSTOM_COMMAND)
|
||||
if frame is None:
|
||||
return
|
||||
fconf = fv.full_config.get()
|
||||
path = fconf.get_path_for_id(config[CONF_MODBUS_CONTROLLER_ID])[:-1]
|
||||
controller = fconf.get_config_for_path(path)
|
||||
# the controller's DEVICE address (from modbus_device_schema)
|
||||
address = controller[CONF_ADDRESS]
|
||||
if frame[0] != address:
|
||||
raise cv.Invalid(
|
||||
f"'custom_command' begins with device address {frame[0]:#04x}, but this sensor's "
|
||||
f"modbus_controller uses address {address:#04x}. 'custom_command' is renamed to "
|
||||
f"'custom_pdu', which is always sent to the controller's own address. Drop the leading "
|
||||
f"address byte and use 'custom_pdu' if {address:#04x} is correct, or move this sensor to "
|
||||
f"the modbus_controller for device {frame[0]:#04x}.",
|
||||
[CONF_CUSTOM_COMMAND],
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"[modbus_controller] 'custom_command' is deprecated and will be removed in 2027.3.0; "
|
||||
"auto-migrated to 'custom_pdu' (dropped the leading device address byte). Rename the key "
|
||||
"and drop that byte to silence this warning."
|
||||
)
|
||||
config[CONF_CUSTOM_PDU] = list(frame[1:])
|
||||
del config[CONF_CUSTOM_COMMAND]
|
||||
|
||||
|
||||
def _final_validate(config: ConfigType) -> None:
|
||||
modbus.final_validate_modbus_device("modbus_controller", role="client")(config)
|
||||
|
||||
@@ -156,7 +255,7 @@ def modbus_calc_properties(config):
|
||||
value_type = config[CONF_VALUE_TYPE]
|
||||
if reg_count == 0:
|
||||
reg_count = TYPE_REGISTER_MAP[value_type]
|
||||
if CONF_CUSTOM_COMMAND in config:
|
||||
if CONF_CUSTOM_PDU in config:
|
||||
if CONF_ADDRESS not in config:
|
||||
# generate a unique modbus address using the hash of the name
|
||||
# CONF_NAME set even if only CONF_ID is used.
|
||||
@@ -173,8 +272,8 @@ def modbus_calc_properties(config):
|
||||
async def add_modbus_base_properties(
|
||||
var, config, sensor_type, lambda_param_type=cg.float_, lambda_return_type=float
|
||||
):
|
||||
if CONF_CUSTOM_COMMAND in config:
|
||||
cg.add(var.set_custom_data(config[CONF_CUSTOM_COMMAND]))
|
||||
if CONF_CUSTOM_PDU in config:
|
||||
cg.add(var.set_custom_pdu(config[CONF_CUSTOM_PDU]))
|
||||
|
||||
if config[CONF_RESPONSE_SIZE] > 0:
|
||||
cg.add(var.set_register_size(config[CONF_RESPONSE_SIZE]))
|
||||
|
||||
@@ -8,6 +8,7 @@ from .. import (
|
||||
ModbusItemBaseSchema,
|
||||
SensorItem,
|
||||
add_modbus_base_properties,
|
||||
migrate_custom_command,
|
||||
modbus_calc_properties,
|
||||
modbus_controller_ns,
|
||||
validate_modbus_register,
|
||||
@@ -17,7 +18,6 @@ from ..const import (
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_REGISTER_TYPE,
|
||||
CONF_SKIP_UPDATES,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["modbus_controller"]
|
||||
@@ -40,6 +40,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
validate_modbus_register,
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = migrate_custom_command
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
byte_offset, _ = modbus_calc_properties(config)
|
||||
@@ -49,7 +51,6 @@ async def to_code(config):
|
||||
config[CONF_ADDRESS],
|
||||
byte_offset,
|
||||
config[CONF_BITMASK],
|
||||
config[CONF_SKIP_UPDATES],
|
||||
config[CONF_FORCE_NEW_RANGE],
|
||||
)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -11,13 +11,12 @@ namespace esphome::modbus_controller {
|
||||
class ModbusBinarySensor final : public Component, public binary_sensor::BinarySensor, public SensorItem {
|
||||
public:
|
||||
ModbusBinarySensor(modbus::EntityType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
|
||||
uint16_t skip_updates, bool force_new_range) {
|
||||
bool force_new_range) {
|
||||
this->register_type = register_type;
|
||||
this->set_address(start_address);
|
||||
this->set_offset_from_start_address(offset);
|
||||
this->bitmask = bitmask;
|
||||
this->sensor_value_type = SensorValueType::BIT;
|
||||
this->skip_updates = skip_updates;
|
||||
this->force_new_range = force_new_range;
|
||||
|
||||
if (modbus::helpers::is_entity_type_binary(register_type)) {
|
||||
|
||||
@@ -4,6 +4,7 @@ CONF_BYTE_OFFSET = "byte_offset"
|
||||
CONF_COMMAND_THROTTLE = "command_throttle"
|
||||
CONF_OFFLINE_SKIP_UPDATES = "offline_skip_updates"
|
||||
CONF_CUSTOM_COMMAND = "custom_command"
|
||||
CONF_CUSTOM_PDU = "custom_pdu"
|
||||
CONF_FORCE_NEW_RANGE = "force_new_range"
|
||||
CONF_MAX_CMD_RETRIES = "max_cmd_retries"
|
||||
CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id"
|
||||
|
||||
@@ -14,7 +14,6 @@ ModbusCommandItem::ModbusCommandItem(ModbusController &controller, modbus::Modbu
|
||||
RegisterRange &&range)
|
||||
: modbus::ModbusClientDevice(parent, address),
|
||||
sensors(std::move(range.sensors)),
|
||||
skip_updates(range.skip_updates),
|
||||
register_type_(range.register_type),
|
||||
start_address_(range.start_address),
|
||||
register_count_(range.register_count),
|
||||
@@ -24,12 +23,14 @@ ModbusCommandItem::ModbusCommandItem(ModbusController &controller, modbus::Modbu
|
||||
ModbusCommandItem::ModbusCommandItem(ModbusController &controller, modbus::ModbusClientHub *parent, uint8_t address,
|
||||
SensorItem *sensor)
|
||||
: modbus::ModbusClientDevice(parent, address),
|
||||
skip_updates(sensor->skip_updates),
|
||||
start_address_(sensor->start_address),
|
||||
register_count_(sensor->register_count),
|
||||
function_code_(FunctionCode::CUSTOM),
|
||||
custom_data_(&sensor->custom_data),
|
||||
custom_pdu_(&sensor->custom_pdu),
|
||||
controller_(&controller) {
|
||||
// The PDU's first byte is its real function code; carry it so dump_config, the on_command_sent
|
||||
// trigger and the response callbacks report the actual code instead of CUSTOM.
|
||||
if (!sensor->custom_pdu.empty())
|
||||
this->function_code_ = static_cast<FunctionCode>(sensor->custom_pdu.data()[0]);
|
||||
this->sensors.insert(sensor);
|
||||
}
|
||||
|
||||
@@ -40,13 +41,12 @@ ModbusCommandItem::ModbusCommandItem(ModbusController &controller, modbus::Modbu
|
||||
ModbusCommandItem::ModbusCommandItem(const ModbusCommandItem &other)
|
||||
: modbus::ModbusClientDevice(other.parent_, other.address_),
|
||||
sensors(other.sensors),
|
||||
skip_updates(other.skip_updates),
|
||||
on_data_func(other.on_data_func),
|
||||
register_type_(other.register_type_),
|
||||
start_address_(other.start_address_),
|
||||
register_count_(other.register_count_),
|
||||
function_code_(other.function_code_),
|
||||
custom_data_(other.custom_data_),
|
||||
custom_pdu_(other.custom_pdu_),
|
||||
controller_(other.controller_) {
|
||||
// SmallInlineBuffer is move-only, so deep-copy the bytes explicitly.
|
||||
this->payload.set(other.payload.data(), other.payload.size());
|
||||
@@ -55,14 +55,13 @@ ModbusCommandItem::ModbusCommandItem(const ModbusCommandItem &other)
|
||||
ModbusCommandItem::ModbusCommandItem(ModbusCommandItem &&other) noexcept
|
||||
: modbus::ModbusClientDevice(other.parent_, other.address_),
|
||||
sensors(std::move(other.sensors)),
|
||||
skip_updates(other.skip_updates),
|
||||
on_data_func(std::move(other.on_data_func)),
|
||||
payload(std::move(other.payload)),
|
||||
register_type_(other.register_type_),
|
||||
start_address_(other.start_address_),
|
||||
register_count_(other.register_count_),
|
||||
function_code_(other.function_code_),
|
||||
custom_data_(other.custom_data_),
|
||||
custom_pdu_(other.custom_pdu_),
|
||||
controller_(other.controller_) {
|
||||
other.parent_ = nullptr;
|
||||
}
|
||||
@@ -74,11 +73,14 @@ void ModbusCommandItem::on_response(std::span<const uint8_t> request_pdu, std::s
|
||||
auto data = modbus::helpers::server_pdu_payload(response_pdu);
|
||||
if (this->on_data_func) {
|
||||
this->on_data_func(this->register_type_, this->start_address_, data);
|
||||
} else if (modbus::helpers::is_function_code_write(static_cast<uint8_t>(this->function_code_))) {
|
||||
// write acknowledgement - nothing to publish
|
||||
} else {
|
||||
} else if (!this->sensors.empty()) {
|
||||
// A polling command always has sensors; a factory/write command never does. Test this before the
|
||||
// write-code branch so a custom_pdu whose function code is a write (e.g. 0x17, whose response
|
||||
// carries read data) still reaches its sensor instead of being treated as a bare write ack.
|
||||
for (auto *sensor : this->sensors)
|
||||
sensor->parse_and_publish(data);
|
||||
} else if (modbus::helpers::is_function_code_write(static_cast<uint8_t>(this->function_code_))) {
|
||||
// write acknowledgement - nothing to publish
|
||||
}
|
||||
if (this->controller_ != nullptr)
|
||||
this->controller_->unqueue_command(this);
|
||||
@@ -116,13 +118,11 @@ void ModbusCommandItem::on_sent(std::span<const uint8_t> request_pdu) {
|
||||
// on_sent is this command's only callback, so drop the one-shot from the queue here, or it would leak.
|
||||
// Test the address the frame went to, not address_: a custom command's frame carries its own address
|
||||
// (frame[0]), which may differ from this controller's. (unqueue_command() is a no-op for a poll.)
|
||||
// A custom polling command sends its PDU to this controller's own address, so only a factory custom
|
||||
// command (a raw frame staged in payload) can carry a different address byte.
|
||||
uint8_t wire_address = this->address_;
|
||||
if (this->function_code_ == FunctionCode::CUSTOM) {
|
||||
std::span<const uint8_t> frame =
|
||||
this->custom_data_ != nullptr ? std::span<const uint8_t>(*this->custom_data_) : this->payload;
|
||||
if (!frame.empty())
|
||||
wire_address = frame[0];
|
||||
}
|
||||
if (this->function_code_ == FunctionCode::CUSTOM && !this->payload.empty())
|
||||
wire_address = this->payload.data()[0];
|
||||
if (wire_address == modbus::BROADCAST_ADDRESS)
|
||||
this->controller_->unqueue_command(this);
|
||||
}
|
||||
@@ -194,23 +194,11 @@ void ModbusController::sweep_completed_one_shots_() {
|
||||
[](const std::unique_ptr<ModbusCommandItem> &item) { return item->pending_removal; });
|
||||
}
|
||||
|
||||
void ModbusController::update_range_(ModbusCommandItem &cmd) {
|
||||
if (this->update_counter_ % (cmd.skip_updates + 1) != 0) {
|
||||
ESP_LOGVV(TAG, "Skipping update for range 0x%X", cmd.register_address());
|
||||
return;
|
||||
}
|
||||
// A refusal is already logged by the hub; note the affected range for controller-level diagnostics.
|
||||
if (!cmd.send()) {
|
||||
ESP_LOGD(TAG, "Poll refused by hub for range 0x%X", cmd.register_address());
|
||||
}
|
||||
}
|
||||
|
||||
void ModbusController::update() {
|
||||
this->sweep_completed_one_shots_(); // reclaim one-shots deferred out of their own callbacks
|
||||
if (this->module_offline_) {
|
||||
// Offline probing follows the offline cadence alone; per-range skip_updates resumes once the
|
||||
// device is back online. Requiring both cadences to coincide would leave phase combinations
|
||||
// where a probe never goes out.
|
||||
// Offline probing follows the offline cadence alone; regular every-update polling resumes once
|
||||
// the device is back online.
|
||||
if (offline_retry_due(this->update_counter_, this->module_offline_at_, this->offline_skip_updates_)) {
|
||||
ESP_LOGV(TAG, "Module offline - retrying");
|
||||
this->cmd_non_responses_ = 0; // allow the probe through can_send()
|
||||
@@ -229,7 +217,9 @@ void ModbusController::update() {
|
||||
if (this->can_send()) {
|
||||
for (auto &cmd : this->polling_command_items_) {
|
||||
ESP_LOGVV(TAG, "Updating range 0x%X", cmd.register_address());
|
||||
this->update_range_(cmd);
|
||||
// A refusal is already logged by the hub; note the affected range for controller-level diagnostics.
|
||||
if (!cmd.send())
|
||||
ESP_LOGD(TAG, "Poll refused by hub for range 0x%X", cmd.register_address());
|
||||
}
|
||||
}
|
||||
this->update_counter_++;
|
||||
@@ -264,8 +254,8 @@ void ModbusController::create_polling_commands_() {
|
||||
bool range_custom_size = false;
|
||||
SensorItem *prev = nullptr;
|
||||
for (SensorItem *curr : this->sensorset_) {
|
||||
ESP_LOGV(TAG, "Register: 0x%X count=%d size=%zu offset=%u skip=%u addr=%p", curr->start_address,
|
||||
curr->register_count, curr->get_register_size(), curr->offset, curr->skip_updates, curr);
|
||||
ESP_LOGV(TAG, "Register: 0x%X count=%d size=%zu offset=%u addr=%p", curr->start_address, curr->register_count,
|
||||
curr->get_register_size(), curr->offset, curr);
|
||||
|
||||
const bool custom_size = curr->get_register_size() != static_cast<size_t>(curr->register_count) * 2;
|
||||
|
||||
@@ -296,14 +286,12 @@ void ModbusController::create_polling_commands_() {
|
||||
ESP_LOGV(TAG, "Extend range to include 0x%X", curr->start_address);
|
||||
} else if (range_shared && !range_forced && curr->start_address >= r.start_address &&
|
||||
curr->start_address + curr->register_count <= r.start_address + r.register_count &&
|
||||
!range_custom_size && !custom_size && curr->skip_updates == r.skip_updates) {
|
||||
!range_custom_size && !custom_size) {
|
||||
// The registers already fall inside a range that a shared-address join widened, so this sensor
|
||||
// reads its slice of that response instead of adding an overlapping second poll. The guards keep
|
||||
// it narrow: only a widened range, never a force-isolated one; only where every register in the
|
||||
// range returns two bytes, so interior positions follow from the addresses; only sensors genuinely
|
||||
// inside it, which is why the lower bound is needed given the walk is not address-ordered; and
|
||||
// only where the polling rates already match, since joining runs this sensor through the rate
|
||||
// merge below and would otherwise change one of them.
|
||||
// inside it, which is why the lower bound is needed given the walk is not address-ordered.
|
||||
const uint16_t addr_delta = curr->start_address - r.start_address;
|
||||
curr->offset = static_cast<uint8_t>((curr->addresses_bits() ? addr_delta : addr_delta * 2) +
|
||||
curr->offset_from_start_address);
|
||||
@@ -329,7 +317,7 @@ void ModbusController::create_polling_commands_() {
|
||||
|
||||
if (!join) {
|
||||
if (have_range) {
|
||||
ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates);
|
||||
ESP_LOGV(TAG, "Add range 0x%X %d", r.start_address, r.register_count);
|
||||
this->create_polling_command_(std::move(r));
|
||||
}
|
||||
r = {};
|
||||
@@ -341,11 +329,7 @@ void ModbusController::create_polling_commands_() {
|
||||
r.start_address = curr->start_address;
|
||||
r.register_count = curr->register_count;
|
||||
r.register_type = curr->register_type;
|
||||
r.skip_updates = curr->skip_updates;
|
||||
have_range = true;
|
||||
} else if (curr->skip_updates != 0) {
|
||||
// use the lowest non-zero skip_updates for the whole range (0 is the default and is excluded)
|
||||
r.skip_updates = (r.skip_updates != 0) ? std::min(r.skip_updates, curr->skip_updates) : curr->skip_updates;
|
||||
}
|
||||
|
||||
// Every member records its range's first register. The resolved offset is relative to it, so the
|
||||
@@ -355,7 +339,7 @@ void ModbusController::create_polling_commands_() {
|
||||
prev = curr;
|
||||
}
|
||||
if (have_range) {
|
||||
ESP_LOGV(TAG, "Add last range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates);
|
||||
ESP_LOGV(TAG, "Add last range 0x%X %d", r.start_address, r.register_count);
|
||||
this->create_polling_command_(std::move(r));
|
||||
}
|
||||
// Reclaim growth slack; safe here because nothing has registered with the hub yet (see the
|
||||
@@ -380,8 +364,8 @@ void ModbusController::dump_config() {
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "ranges");
|
||||
for (auto &it : this->polling_command_items_) {
|
||||
ESP_LOGCONFIG(TAG, " Range type=%u start=0x%X count=%d skip_updates=%d", static_cast<uint8_t>(it.register_type()),
|
||||
it.register_address(), it.register_count(), it.skip_updates);
|
||||
ESP_LOGCONFIG(TAG, " Range type=%u start=0x%X count=%d", static_cast<uint8_t>(it.register_type()),
|
||||
it.register_address(), it.register_count());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -513,17 +497,19 @@ ModbusCommandItem ModbusCommandItem::create_custom_command(
|
||||
|
||||
bool ModbusCommandItem::send() {
|
||||
bool accepted;
|
||||
if (this->function_code_ != FunctionCode::CUSTOM) {
|
||||
if (this->custom_pdu_ != nullptr) {
|
||||
// Custom polling command: send the sensor's ready-made PDU (function code + data, no address byte)
|
||||
// to this controller's own device address; the hub prepends the address and appends the CRC.
|
||||
accepted = modbus::ModbusClientDevice::queue_pdu(std::span<const uint8_t>(*this->custom_pdu_));
|
||||
} else if (this->function_code_ != FunctionCode::CUSTOM) {
|
||||
accepted = this->queue_pdu(modbus::helpers::create_client_pdu(
|
||||
this->function_code_, this->start_address_, this->register_count_,
|
||||
this->payload.empty() ? nullptr : this->payload.data(), this->payload.size()));
|
||||
} else {
|
||||
// Custom command: the bytes are a complete raw frame (address + PDU). Send the PDU to the frame's own
|
||||
// address (which may differ from this controller's); the hub appends the CRC and routes the response
|
||||
// back to this item by pointer. (send_raw() is deprecated, so queue_pdu() is called with the extracted
|
||||
// address. Raw-frame semantics are kept here; the custom_pdu migration is a later step.)
|
||||
std::span<const uint8_t> frame =
|
||||
this->custom_data_ != nullptr ? std::span<const uint8_t>(*this->custom_data_) : this->payload;
|
||||
// Factory custom command: payload holds a complete raw frame (address + PDU). Send the PDU to the
|
||||
// frame's own address (which may differ from this controller's); the hub appends the CRC and routes
|
||||
// the response back to this item by pointer.
|
||||
std::span<const uint8_t> frame = this->payload;
|
||||
if (frame.empty()) {
|
||||
ESP_LOGW(TAG, "Empty custom command frame, not sent");
|
||||
accepted = false;
|
||||
|
||||
@@ -157,7 +157,7 @@ class SensorItem {
|
||||
this->range_start_address = address;
|
||||
}
|
||||
|
||||
void set_custom_data(const std::vector<uint8_t> &data) { custom_data = data; }
|
||||
void set_custom_pdu(std::initializer_list<uint8_t> pdu) { this->custom_pdu.set(pdu.begin(), pdu.size()); }
|
||||
size_t virtual get_register_size() const {
|
||||
if (this->addresses_bits()) {
|
||||
return 1;
|
||||
@@ -186,8 +186,7 @@ class SensorItem {
|
||||
uint8_t offset_from_start_address{0};
|
||||
/// First register of the range this sensor is polled in; equals start_address for an unpolled item.
|
||||
uint16_t range_start_address{0};
|
||||
uint16_t skip_updates{0};
|
||||
std::vector<uint8_t> custom_data{};
|
||||
SmallInlineBuffer<8> custom_pdu{};
|
||||
bool force_new_range{false};
|
||||
};
|
||||
|
||||
@@ -230,8 +229,7 @@ struct RegisterRange {
|
||||
uint16_t start_address;
|
||||
modbus::EntityType register_type;
|
||||
uint8_t register_count;
|
||||
uint16_t skip_updates; // the config value
|
||||
SensorSet sensors; // all sensors of this range
|
||||
SensorSet sensors; // all sensors of this range
|
||||
};
|
||||
|
||||
/// A single modbus command. Each command is its own ModbusClientDevice: it sends its frame to the hub
|
||||
@@ -257,7 +255,6 @@ class ModbusCommandItem : public modbus::ModbusClientDevice {
|
||||
ModbusCommandItem &operator=(ModbusCommandItem &&) = delete;
|
||||
|
||||
SensorSet sensors; // sensors served by this command (empty for factory/write commands)
|
||||
uint16_t skip_updates{0};
|
||||
std::function<void(EntityType register_type, uint16_t start_address, std::span<const uint8_t> data)> on_data_func;
|
||||
/// Write data bytes for the command (register/coil values), or the raw frame of a one-shot custom
|
||||
/// command; reads leave it empty. Small-buffer optimized: fixed-size commands (single-register/coil
|
||||
@@ -378,7 +375,7 @@ class ModbusCommandItem : public modbus::ModbusClientDevice {
|
||||
uint16_t register_count_{0};
|
||||
FunctionCode function_code_{FunctionCode::CUSTOM};
|
||||
/// Custom polling commands reference the PDU bytes owned by their SensorItem instead of copying them.
|
||||
const std::vector<uint8_t> *custom_data_{nullptr};
|
||||
const SmallInlineBuffer<8> *custom_pdu_{nullptr};
|
||||
ModbusController *controller_{nullptr};
|
||||
};
|
||||
|
||||
@@ -462,19 +459,15 @@ class ModbusController final : public PollingComponent {
|
||||
void create_polling_commands_();
|
||||
/// build one persistent polling command from a range and add it to polling_command_items_
|
||||
void create_polling_command_(RegisterRange &&range) {
|
||||
// A custom range polls the first sensor's custom_data (a ready-made raw frame); it needs the
|
||||
// sensor constructor so the command references those bytes and decodes the real function code.
|
||||
// The response still dispatches to every sensor in the range.
|
||||
// A custom range polls the first sensor's custom_pdu (referenced, not copied); the sensor constructor
|
||||
// decodes the real function code. The response still dispatches to every sensor in the range.
|
||||
if (range.register_type == EntityType::CUSTOM && !range.sensors.empty()) {
|
||||
auto &cmd = this->polling_command_items_.emplace_back(*this, this->hub_, this->address_, *range.sensors.begin());
|
||||
cmd.sensors = std::move(range.sensors);
|
||||
cmd.skip_updates = range.skip_updates; // the range's merged rate, not the first sensor's
|
||||
} else {
|
||||
this->polling_command_items_.emplace_back(*this, this->hub_, this->address_, std::move(range));
|
||||
}
|
||||
}
|
||||
/// send a range's polling command if it is due this update
|
||||
void update_range_(ModbusCommandItem &cmd);
|
||||
/// The hub this controller's commands/entities send through, and the modbus address they target.
|
||||
modbus::ModbusClientHub *hub_{nullptr};
|
||||
uint8_t address_{0};
|
||||
@@ -496,7 +489,7 @@ class ModbusController final : public PollingComponent {
|
||||
bool module_offline_{false};
|
||||
/// update_counter_ value at which the module went offline (for offline_skip_updates timing)
|
||||
uint16_t module_offline_at_{0};
|
||||
/// counts update() cycles; drives skip_updates and offline timing
|
||||
/// counts update() cycles; drives the offline-retry cadence
|
||||
uint16_t update_counter_{0};
|
||||
/// consecutive non-responses; drives can_send() and offline detection
|
||||
uint8_t cmd_non_responses_{0};
|
||||
|
||||
@@ -18,16 +18,17 @@ from .. import (
|
||||
ModbusItemBaseSchema,
|
||||
SensorItem,
|
||||
add_modbus_base_properties,
|
||||
migrate_custom_command,
|
||||
modbus_calc_properties,
|
||||
modbus_controller_ns,
|
||||
)
|
||||
from ..const import (
|
||||
CONF_BITMASK,
|
||||
CONF_CUSTOM_COMMAND,
|
||||
CONF_CUSTOM_PDU,
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_REGISTER_TYPE,
|
||||
CONF_SKIP_UPDATES,
|
||||
CONF_USE_WRITE_MULTIPLE,
|
||||
CONF_VALUE_TYPE,
|
||||
CONF_WRITE_LAMBDA,
|
||||
@@ -53,9 +54,11 @@ def validate_min_max(config):
|
||||
|
||||
|
||||
def validate_modbus_number(config):
|
||||
if CONF_CUSTOM_COMMAND not in config and CONF_ADDRESS not in config:
|
||||
# custom_command is the deprecated alias for custom_pdu (migrated later in final validate).
|
||||
has_custom = CONF_CUSTOM_PDU in config or CONF_CUSTOM_COMMAND in config
|
||||
if not has_custom and CONF_ADDRESS not in config:
|
||||
raise cv.Invalid(
|
||||
f" {CONF_ADDRESS} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used"
|
||||
f" {CONF_ADDRESS} is a required property if '{CONF_CUSTOM_PDU}:' isn't used"
|
||||
)
|
||||
return config
|
||||
|
||||
@@ -83,6 +86,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
validate_modbus_number,
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = migrate_custom_command
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
byte_offset, reg_count = modbus_calc_properties(config)
|
||||
@@ -94,7 +99,6 @@ async def to_code(config):
|
||||
config[CONF_BITMASK],
|
||||
config[CONF_VALUE_TYPE],
|
||||
reg_count,
|
||||
config[CONF_SKIP_UPDATES],
|
||||
config[CONF_FORCE_NEW_RANGE],
|
||||
)
|
||||
|
||||
|
||||
@@ -13,14 +13,13 @@ using value_to_data_t = std::function<float>(float);
|
||||
class ModbusNumber final : public number::Number, public Component, public SensorItem {
|
||||
public:
|
||||
ModbusNumber(modbus::EntityType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
|
||||
SensorValueType value_type, int register_count, uint16_t skip_updates, bool force_new_range) {
|
||||
SensorValueType value_type, int register_count, bool force_new_range) {
|
||||
this->register_type = register_type;
|
||||
this->set_address(start_address);
|
||||
this->set_offset_from_start_address(offset);
|
||||
this->bitmask = bitmask;
|
||||
this->sensor_value_type = value_type;
|
||||
this->register_count = register_count;
|
||||
this->skip_updates = skip_updates;
|
||||
this->force_new_range = force_new_range;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from .. import (
|
||||
)
|
||||
from ..const import (
|
||||
CONF_CUSTOM_COMMAND,
|
||||
CONF_CUSTOM_PDU,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_REGISTER_TYPE,
|
||||
CONF_USE_WRITE_MULTIPLE,
|
||||
@@ -37,8 +38,11 @@ CONFIG_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ModbusBinaryOutput),
|
||||
cv.Required(CONF_ADDRESS): cv.positive_int,
|
||||
cv.Optional(CONF_CUSTOM_PDU): cv.invalid(
|
||||
"custom_pdu is not supported for outputs; use a write_lambda instead"
|
||||
),
|
||||
cv.Optional(CONF_CUSTOM_COMMAND): cv.invalid(
|
||||
"custom_command is not supported for outputs"
|
||||
"custom_command is not supported for outputs; use a write_lambda instead"
|
||||
),
|
||||
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
|
||||
@@ -48,8 +52,11 @@ CONFIG_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ModbusFloatOutput),
|
||||
cv.Required(CONF_ADDRESS): cv.positive_int,
|
||||
cv.Optional(CONF_CUSTOM_PDU): cv.invalid(
|
||||
"custom_pdu is not supported for outputs; use a write_lambda instead"
|
||||
),
|
||||
cv.Optional(CONF_CUSTOM_COMMAND): cv.invalid(
|
||||
"custom_command is not supported for outputs"
|
||||
"custom_command is not supported for outputs; use a write_lambda instead"
|
||||
),
|
||||
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(
|
||||
SENSOR_VALUE_TYPE
|
||||
|
||||
@@ -17,7 +17,6 @@ class ModbusFloatOutput final : public output::FloatOutput, public Component, pu
|
||||
this->bitmask = 0xFFFFFFFF;
|
||||
this->register_count = register_count;
|
||||
this->sensor_value_type = value_type;
|
||||
this->skip_updates = 0;
|
||||
this->set_address(this->start_address + offset);
|
||||
this->set_offset_from_start_address(0);
|
||||
}
|
||||
@@ -48,7 +47,6 @@ class ModbusBinaryOutput final : public output::BinaryOutput, public Component,
|
||||
this->set_address(start_address);
|
||||
this->bitmask = 0xFFFFFFFF;
|
||||
this->sensor_value_type = SensorValueType::BIT;
|
||||
this->skip_updates = 0;
|
||||
this->register_count = 1;
|
||||
this->set_address(this->start_address + offset);
|
||||
this->set_offset_from_start_address(0);
|
||||
|
||||
@@ -4,7 +4,12 @@ from esphome.components.modbus.helpers import SENSOR_VALUE_TYPE, TYPE_REGISTER_M
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC
|
||||
|
||||
from .. import ModbusController, SensorItem, modbus_controller_ns
|
||||
from .. import (
|
||||
ModbusController,
|
||||
SensorItem,
|
||||
modbus_controller_ns,
|
||||
validate_skip_updates_deprecated,
|
||||
)
|
||||
from ..const import (
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
@@ -69,7 +74,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
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_SKIP_UPDATES): validate_skip_updates_deprecated,
|
||||
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,
|
||||
@@ -95,7 +100,6 @@ async def to_code(config):
|
||||
value_type,
|
||||
config[CONF_ADDRESS],
|
||||
reg_count,
|
||||
config[CONF_SKIP_UPDATES],
|
||||
config[CONF_FORCE_NEW_RANGE],
|
||||
list(options_map.values()),
|
||||
)
|
||||
|
||||
@@ -11,8 +11,8 @@ namespace esphome::modbus_controller {
|
||||
|
||||
class ModbusSelect final : public Component, public select::Select, public SensorItem {
|
||||
public:
|
||||
ModbusSelect(SensorValueType sensor_value_type, uint16_t start_address, uint8_t register_count, uint16_t skip_updates,
|
||||
bool force_new_range, std::vector<int64_t> mapping) {
|
||||
ModbusSelect(SensorValueType sensor_value_type, uint16_t start_address, uint8_t register_count, bool force_new_range,
|
||||
std::vector<int64_t> mapping) {
|
||||
this->register_type = modbus::EntityType::HOLDING; // not configurable
|
||||
this->sensor_value_type = sensor_value_type;
|
||||
this->set_address(start_address);
|
||||
@@ -20,7 +20,6 @@ class ModbusSelect final : public Component, public select::Select, public Senso
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ from .. import (
|
||||
ModbusItemBaseSchema,
|
||||
SensorItem,
|
||||
add_modbus_base_properties,
|
||||
migrate_custom_command,
|
||||
modbus_calc_properties,
|
||||
modbus_controller_ns,
|
||||
validate_modbus_register,
|
||||
@@ -18,7 +19,6 @@ from ..const import (
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_REGISTER_COUNT,
|
||||
CONF_REGISTER_TYPE,
|
||||
CONF_SKIP_UPDATES,
|
||||
CONF_VALUE_TYPE,
|
||||
)
|
||||
|
||||
@@ -44,6 +44,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
validate_modbus_register,
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = migrate_custom_command
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
byte_offset, reg_count = modbus_calc_properties(config)
|
||||
@@ -56,7 +58,6 @@ async def to_code(config):
|
||||
config[CONF_BITMASK],
|
||||
value_type,
|
||||
reg_count,
|
||||
config[CONF_SKIP_UPDATES],
|
||||
config[CONF_FORCE_NEW_RANGE],
|
||||
)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -11,14 +11,13 @@ namespace esphome::modbus_controller {
|
||||
class ModbusSensor final : public Component, public sensor::Sensor, public SensorItem {
|
||||
public:
|
||||
ModbusSensor(modbus::EntityType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
|
||||
SensorValueType value_type, int register_count, uint16_t skip_updates, bool force_new_range) {
|
||||
SensorValueType value_type, int register_count, bool force_new_range) {
|
||||
this->register_type = register_type;
|
||||
this->set_address(start_address);
|
||||
this->set_offset_from_start_address(offset);
|
||||
this->bitmask = bitmask;
|
||||
this->sensor_value_type = value_type;
|
||||
this->register_count = register_count;
|
||||
this->skip_updates = skip_updates;
|
||||
this->force_new_range = force_new_range;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ from .. import (
|
||||
ModbusItemBaseSchema,
|
||||
SensorItem,
|
||||
add_modbus_base_properties,
|
||||
migrate_custom_command,
|
||||
modbus_calc_properties,
|
||||
modbus_controller_ns,
|
||||
validate_modbus_register,
|
||||
@@ -17,7 +18,6 @@ from ..const import (
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_REGISTER_TYPE,
|
||||
CONF_SKIP_UPDATES,
|
||||
CONF_USE_WRITE_MULTIPLE,
|
||||
CONF_WRITE_LAMBDA,
|
||||
)
|
||||
@@ -45,6 +45,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
validate_modbus_register,
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = migrate_custom_command
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
byte_offset, _ = modbus_calc_properties(config)
|
||||
@@ -54,7 +56,6 @@ async def to_code(config):
|
||||
config[CONF_ADDRESS],
|
||||
byte_offset,
|
||||
config[CONF_BITMASK],
|
||||
config[CONF_SKIP_UPDATES],
|
||||
config[CONF_FORCE_NEW_RANGE],
|
||||
)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -11,13 +11,12 @@ namespace esphome::modbus_controller {
|
||||
class ModbusSwitch final : public Component, public switch_::Switch, public SensorItem {
|
||||
public:
|
||||
ModbusSwitch(modbus::EntityType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
|
||||
uint16_t skip_updates, bool force_new_range) {
|
||||
bool force_new_range) {
|
||||
this->register_type = register_type;
|
||||
this->set_address(start_address);
|
||||
this->set_offset_from_start_address(offset);
|
||||
this->bitmask = bitmask;
|
||||
this->sensor_value_type = SensorValueType::BIT;
|
||||
this->skip_updates = skip_updates;
|
||||
this->register_count = 1;
|
||||
if (register_type == modbus::EntityType::HOLDING || register_type == modbus::EntityType::COIL) {
|
||||
this->set_address(this->start_address + offset);
|
||||
|
||||
@@ -8,6 +8,7 @@ from .. import (
|
||||
ModbusItemBaseSchema,
|
||||
SensorItem,
|
||||
add_modbus_base_properties,
|
||||
migrate_custom_command,
|
||||
modbus_calc_properties,
|
||||
modbus_controller_ns,
|
||||
validate_modbus_register,
|
||||
@@ -19,7 +20,6 @@ from ..const import (
|
||||
CONF_REGISTER_COUNT,
|
||||
CONF_REGISTER_TYPE,
|
||||
CONF_RESPONSE_SIZE,
|
||||
CONF_SKIP_UPDATES,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["modbus_controller"]
|
||||
@@ -55,6 +55,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
validate_modbus_register,
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = migrate_custom_command
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
byte_offset, reg_count = modbus_calc_properties(config)
|
||||
@@ -70,7 +72,6 @@ async def to_code(config):
|
||||
reg_count,
|
||||
config[CONF_RESPONSE_SIZE],
|
||||
config[CONF_RAW_ENCODE],
|
||||
config[CONF_SKIP_UPDATES],
|
||||
config[CONF_FORCE_NEW_RANGE],
|
||||
)
|
||||
|
||||
|
||||
@@ -13,14 +13,13 @@ enum class RawEncoding { NONE = 0, HEXBYTES = 1, COMMA = 2, ANSI = 3 };
|
||||
class ModbusTextSensor final : public Component, public text_sensor::TextSensor, public SensorItem {
|
||||
public:
|
||||
ModbusTextSensor(modbus::EntityType register_type, uint16_t start_address, uint8_t offset, uint8_t register_count,
|
||||
uint16_t response_bytes, RawEncoding encode, uint16_t skip_updates, bool force_new_range) {
|
||||
uint16_t response_bytes, RawEncoding encode, bool force_new_range) {
|
||||
this->register_type = register_type;
|
||||
this->set_address(start_address);
|
||||
this->set_offset_from_start_address(offset);
|
||||
this->response_bytes = response_bytes;
|
||||
this->register_count = register_count;
|
||||
this->encode_ = encode;
|
||||
this->skip_updates = skip_updates;
|
||||
this->bitmask = 0xFFFFFFFF;
|
||||
this->sensor_value_type = SensorValueType::RAW;
|
||||
this->force_new_range = force_new_range;
|
||||
|
||||
Reference in New Issue
Block a user