mirror of
https://github.com/esphome/esphome.git
synced 2024-11-10 09:17:46 +01:00
Modbus_controller: Add custom command. (#2680)
This commit is contained in:
parent
e7827a6997
commit
17a37b1de9
13 changed files with 315 additions and 261 deletions
|
@ -1,10 +1,20 @@
|
||||||
|
import binascii
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import modbus
|
from esphome.components import modbus
|
||||||
from esphome.const import CONF_ID, CONF_ADDRESS
|
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_NAME, CONF_LAMBDA, CONF_OFFSET
|
||||||
from esphome.cpp_helpers import logging
|
from esphome.cpp_helpers import logging
|
||||||
from .const import (
|
from .const import (
|
||||||
|
CONF_BITMASK,
|
||||||
|
CONF_BYTE_OFFSET,
|
||||||
CONF_COMMAND_THROTTLE,
|
CONF_COMMAND_THROTTLE,
|
||||||
|
CONF_CUSTOM_COMMAND,
|
||||||
|
CONF_FORCE_NEW_RANGE,
|
||||||
|
CONF_MODBUS_CONTROLLER_ID,
|
||||||
|
CONF_REGISTER_COUNT,
|
||||||
|
CONF_REGISTER_TYPE,
|
||||||
|
CONF_SKIP_UPDATES,
|
||||||
|
CONF_VALUE_TYPE,
|
||||||
)
|
)
|
||||||
|
|
||||||
CODEOWNERS = ["@martgras"]
|
CODEOWNERS = ["@martgras"]
|
||||||
|
@ -37,6 +47,7 @@ MODBUS_FUNCTION_CODE = {
|
||||||
ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType")
|
ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType")
|
||||||
ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType")
|
ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType")
|
||||||
MODBUS_REGISTER_TYPE = {
|
MODBUS_REGISTER_TYPE = {
|
||||||
|
"custom": ModbusRegisterType.CUSTOM,
|
||||||
"coil": ModbusRegisterType.COIL,
|
"coil": ModbusRegisterType.COIL,
|
||||||
"discrete_input": ModbusRegisterType.DISCRETE_INPUT,
|
"discrete_input": ModbusRegisterType.DISCRETE_INPUT,
|
||||||
"holding": ModbusRegisterType.HOLDING,
|
"holding": ModbusRegisterType.HOLDING,
|
||||||
|
@ -95,6 +106,96 @@ CONFIG_SCHEMA = cv.All(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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_OFFSET,
|
||||||
|
"offset",
|
||||||
|
f"{CONF_OFFSET} and {CONF_BYTE_OFFSET} can't be used together",
|
||||||
|
): cv.positive_int,
|
||||||
|
cv.Exclusive(
|
||||||
|
CONF_BYTE_OFFSET,
|
||||||
|
"offset",
|
||||||
|
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_FORCE_NEW_RANGE, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_modbus_register(config):
|
||||||
|
if CONF_CUSTOM_COMMAND not in config and CONF_ADDRESS not in config:
|
||||||
|
raise cv.Invalid(
|
||||||
|
f" {CONF_ADDRESS} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used"
|
||||||
|
)
|
||||||
|
if CONF_CUSTOM_COMMAND in config and CONF_REGISTER_TYPE in config:
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"can't use '{CONF_REGISTER_TYPE}:' together with '{CONF_CUSTOM_COMMAND}:'",
|
||||||
|
)
|
||||||
|
|
||||||
|
if CONF_CUSTOM_COMMAND not in config 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"
|
||||||
|
)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def modbus_calc_properties(config):
|
||||||
|
byte_offset = 0
|
||||||
|
reg_count = 0
|
||||||
|
if CONF_OFFSET in config:
|
||||||
|
byte_offset = config[CONF_OFFSET]
|
||||||
|
# A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
|
||||||
|
if CONF_BYTE_OFFSET in config:
|
||||||
|
byte_offset = config[CONF_BYTE_OFFSET]
|
||||||
|
if CONF_REGISTER_COUNT in config:
|
||||||
|
reg_count = config[CONF_REGISTER_COUNT]
|
||||||
|
if CONF_VALUE_TYPE in 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_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.
|
||||||
|
# a modbus register address is required to add the item to sensormap
|
||||||
|
value = config[CONF_NAME]
|
||||||
|
if isinstance(value, str):
|
||||||
|
value = value.encode()
|
||||||
|
config[CONF_ADDRESS] = binascii.crc_hqx(value, 0)
|
||||||
|
config[CONF_REGISTER_TYPE] = ModbusRegisterType.CUSTOM
|
||||||
|
config[CONF_FORCE_NEW_RANGE] = True
|
||||||
|
return byte_offset, reg_count
|
||||||
|
|
||||||
|
|
||||||
|
async def add_modbus_base_properties(
|
||||||
|
var, config, sensor_type, lamdba_param_type=cg.float_, lamdba_return_type=float
|
||||||
|
):
|
||||||
|
if CONF_CUSTOM_COMMAND in config:
|
||||||
|
cg.add(var.set_custom_data(config[CONF_CUSTOM_COMMAND]))
|
||||||
|
|
||||||
|
if CONF_LAMBDA in config:
|
||||||
|
template_ = await cg.process_lambda(
|
||||||
|
config[CONF_LAMBDA],
|
||||||
|
[
|
||||||
|
(sensor_type.operator("ptr"), "item"),
|
||||||
|
(lamdba_param_type, "x"),
|
||||||
|
(
|
||||||
|
cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
|
||||||
|
"data",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
return_type=cg.optional.template(lamdba_return_type),
|
||||||
|
)
|
||||||
|
cg.add(var.set_template(template_))
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID], config[CONF_COMMAND_THROTTLE])
|
var = cg.new_Pvariable(config[CONF_ID], config[CONF_COMMAND_THROTTLE])
|
||||||
cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE]))
|
cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE]))
|
||||||
|
@ -119,11 +220,3 @@ def function_code_to_register(function_code):
|
||||||
"write_multiple_registers": ModbusRegisterType.HOLDING,
|
"write_multiple_registers": ModbusRegisterType.HOLDING,
|
||||||
}
|
}
|
||||||
return FUNCTION_CODE_TYPE_MAP[function_code]
|
return FUNCTION_CODE_TYPE_MAP[function_code]
|
||||||
|
|
||||||
|
|
||||||
def find_by_value(dict, find_value):
|
|
||||||
for (key, value) in MODBUS_REGISTER_TYPE.items():
|
|
||||||
print(find_value, value)
|
|
||||||
if find_value == value:
|
|
||||||
return key
|
|
||||||
return "not found"
|
|
||||||
|
|
|
@ -2,16 +2,18 @@ from esphome.components import binary_sensor
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
|
||||||
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OFFSET
|
from esphome.const import CONF_ADDRESS, CONF_ID
|
||||||
from .. import (
|
from .. import (
|
||||||
SensorItem,
|
add_modbus_base_properties,
|
||||||
modbus_controller_ns,
|
modbus_controller_ns,
|
||||||
ModbusController,
|
modbus_calc_properties,
|
||||||
|
validate_modbus_register,
|
||||||
|
ModbusItemBaseSchema,
|
||||||
|
SensorItem,
|
||||||
MODBUS_REGISTER_TYPE,
|
MODBUS_REGISTER_TYPE,
|
||||||
)
|
)
|
||||||
from ..const import (
|
from ..const import (
|
||||||
CONF_BITMASK,
|
CONF_BITMASK,
|
||||||
CONF_BYTE_OFFSET,
|
|
||||||
CONF_FORCE_NEW_RANGE,
|
CONF_FORCE_NEW_RANGE,
|
||||||
CONF_MODBUS_CONTROLLER_ID,
|
CONF_MODBUS_CONTROLLER_ID,
|
||||||
CONF_REGISTER_TYPE,
|
CONF_REGISTER_TYPE,
|
||||||
|
@ -27,30 +29,20 @@ ModbusBinarySensor = modbus_controller_ns.class_(
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
binary_sensor.BINARY_SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(ModbusItemBaseSchema)
|
||||||
|
.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(ModbusBinarySensor),
|
cv.GenerateID(): cv.declare_id(ModbusBinarySensor),
|
||||||
cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
|
cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
|
||||||
cv.Required(CONF_ADDRESS): cv.positive_int,
|
|
||||||
cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
|
|
||||||
cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
|
|
||||||
cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
|
|
||||||
cv.Optional(CONF_BITMASK, default=0x1): cv.hex_uint32_t,
|
|
||||||
cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
|
|
||||||
cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
|
|
||||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
),
|
||||||
|
validate_modbus_register,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
byte_offset = 0
|
byte_offset, _ = modbus_calc_properties(config)
|
||||||
if CONF_OFFSET in config:
|
|
||||||
byte_offset = config[CONF_OFFSET]
|
|
||||||
# A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
|
|
||||||
if CONF_BYTE_OFFSET in config:
|
|
||||||
byte_offset = config[CONF_BYTE_OFFSET]
|
|
||||||
var = cg.new_Pvariable(
|
var = cg.new_Pvariable(
|
||||||
config[CONF_ID],
|
config[CONF_ID],
|
||||||
config[CONF_REGISTER_TYPE],
|
config[CONF_REGISTER_TYPE],
|
||||||
|
@ -65,17 +57,4 @@ async def to_code(config):
|
||||||
|
|
||||||
paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
|
paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
|
||||||
cg.add(paren.add_sensor_item(var))
|
cg.add(paren.add_sensor_item(var))
|
||||||
if CONF_LAMBDA in config:
|
await add_modbus_base_properties(var, config, ModbusBinarySensor, cg.float_, bool)
|
||||||
template_ = await cg.process_lambda(
|
|
||||||
config[CONF_LAMBDA],
|
|
||||||
[
|
|
||||||
(ModbusBinarySensor.operator("ptr"), "item"),
|
|
||||||
(cg.float_, "x"),
|
|
||||||
(
|
|
||||||
cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
|
|
||||||
"data",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
return_type=cg.optional.template(bool),
|
|
||||||
)
|
|
||||||
cg.add(var.set_template(template_))
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
CONF_BITMASK = "bitmask"
|
CONF_BITMASK = "bitmask"
|
||||||
CONF_BYTE_OFFSET = "byte_offset"
|
CONF_BYTE_OFFSET = "byte_offset"
|
||||||
CONF_COMMAND_THROTTLE = "command_throttle"
|
CONF_COMMAND_THROTTLE = "command_throttle"
|
||||||
|
CONF_CUSTOM_COMMAND = "custom_command"
|
||||||
CONF_FORCE_NEW_RANGE = "force_new_range"
|
CONF_FORCE_NEW_RANGE = "force_new_range"
|
||||||
CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id"
|
CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id"
|
||||||
CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode"
|
CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode"
|
||||||
|
|
|
@ -28,7 +28,10 @@ bool ModbusController::send_next_command_() {
|
||||||
command->register_address, command->register_count);
|
command->register_address, command->register_count);
|
||||||
command->send();
|
command->send();
|
||||||
this->last_command_timestamp_ = millis();
|
this->last_command_timestamp_ = millis();
|
||||||
if (!command->on_data_func) { // No handler remove from queue directly after sending
|
// remove from queue if no handler is defined or command was sent too often
|
||||||
|
if (!command->on_data_func || command->send_countdown < 1) {
|
||||||
|
ESP_LOGD(TAG, "Modbus command to device=%d register=0x%02X countdown=%d removed from queue after send",
|
||||||
|
this->address_, command->register_address, command->send_countdown);
|
||||||
command_queue_.pop_front();
|
command_queue_.pop_front();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,24 +72,30 @@ void ModbusController::on_modbus_error(uint8_t function_code, uint8_t exception_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address,
|
std::map<uint64_t, SensorItem *>::iterator ModbusController::find_register_(ModbusRegisterType register_type,
|
||||||
const std::vector<uint8_t> &data) {
|
uint16_t start_address) {
|
||||||
ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address);
|
|
||||||
|
|
||||||
auto vec_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) {
|
auto vec_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) {
|
||||||
return (r.start_address == start_address && r.register_type == register_type);
|
return (r.start_address == start_address && r.register_type == register_type);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (vec_it == register_ranges_.end()) {
|
if (vec_it == register_ranges_.end()) {
|
||||||
ESP_LOGE(TAG, "Handle incoming data : No matching range for sensor found - start_address : 0x%X", start_address);
|
ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address);
|
||||||
return;
|
} else {
|
||||||
}
|
auto map_it = sensormap_.find(vec_it->first_sensorkey);
|
||||||
auto map_it = sensormap_.find(vec_it->first_sensorkey);
|
if (map_it == sensormap_.end()) {
|
||||||
if (map_it == sensormap_.end()) {
|
ESP_LOGE(TAG, "No sensor found in at start_address : 0x%X (0x%llX)", start_address, vec_it->first_sensorkey);
|
||||||
ESP_LOGE(TAG, "Handle incoming data : No sensor found in at start_address : 0x%X (0x%llX)", start_address,
|
} else {
|
||||||
vec_it->first_sensorkey);
|
return sensormap_.find(vec_it->first_sensorkey);
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
|
// not found
|
||||||
|
return std::end(sensormap_);
|
||||||
|
}
|
||||||
|
void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address,
|
||||||
|
const std::vector<uint8_t> &data) {
|
||||||
|
ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address);
|
||||||
|
|
||||||
|
auto map_it = find_register_(register_type, start_address);
|
||||||
// loop through all sensors with the same start address
|
// loop through all sensors with the same start address
|
||||||
while (map_it != sensormap_.end() && map_it->second->start_address == start_address) {
|
while (map_it != sensormap_.end() && map_it->second->start_address == start_address) {
|
||||||
if (map_it->second->register_type == register_type) {
|
if (map_it->second->register_type == register_type) {
|
||||||
|
@ -116,9 +125,23 @@ void ModbusController::update_range_(RegisterRange &r) {
|
||||||
ESP_LOGV(TAG, "Range : %X Size: %x (%d) skip: %d", r.start_address, r.register_count, (int) r.register_type,
|
ESP_LOGV(TAG, "Range : %X Size: %x (%d) skip: %d", r.start_address, r.register_count, (int) r.register_type,
|
||||||
r.skip_updates_counter);
|
r.skip_updates_counter);
|
||||||
if (r.skip_updates_counter == 0) {
|
if (r.skip_updates_counter == 0) {
|
||||||
ModbusCommandItem command_item =
|
// if a custom command is used the user supplied custom_data is only available in the SensorItem.
|
||||||
ModbusCommandItem::create_read_command(this, r.register_type, r.start_address, r.register_count);
|
if (r.register_type == ModbusRegisterType::CUSTOM) {
|
||||||
queue_command(command_item);
|
auto it = this->find_register_(r.register_type, r.start_address);
|
||||||
|
if (it != sensormap_.end()) {
|
||||||
|
auto command_item = ModbusCommandItem::create_custom_command(
|
||||||
|
this, it->second->custom_data,
|
||||||
|
[this](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
|
||||||
|
this->on_register_data(ModbusRegisterType::CUSTOM, start_address, data);
|
||||||
|
});
|
||||||
|
command_item.register_address = it->second->start_address;
|
||||||
|
command_item.register_count = it->second->register_count;
|
||||||
|
command_item.function_code = ModbusFunctionCode::CUSTOM;
|
||||||
|
queue_command(command_item);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
queue_command(ModbusCommandItem::create_read_command(this, r.register_type, r.start_address, r.register_count));
|
||||||
|
}
|
||||||
r.skip_updates_counter = r.skip_updates; // reset counter to config value
|
r.skip_updates_counter = r.skip_updates; // reset counter to config value
|
||||||
} else {
|
} else {
|
||||||
r.skip_updates_counter--;
|
r.skip_updates_counter--;
|
||||||
|
@ -422,6 +445,7 @@ bool ModbusCommandItem::send() {
|
||||||
modbusdevice->send_raw(this->payload);
|
modbusdevice->send_raw(this->payload);
|
||||||
}
|
}
|
||||||
ESP_LOGV(TAG, "Command sent %d 0x%X %d", uint8_t(this->function_code), this->register_address, this->register_count);
|
ESP_LOGV(TAG, "Command sent %d 0x%X %d", uint8_t(this->function_code), this->register_address, this->register_count);
|
||||||
|
send_countdown--;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,6 +573,9 @@ float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_
|
||||||
ESP_LOGD(TAG, "FP32_R = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value);
|
ESP_LOGD(TAG, "FP32_R = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value);
|
||||||
result = raw_to_float.float_value;
|
result = raw_to_float.float_value;
|
||||||
} break;
|
} break;
|
||||||
|
case SensorValueType::RAW:
|
||||||
|
result = NAN;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -247,18 +247,11 @@ float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_
|
||||||
|
|
||||||
class ModbusController;
|
class ModbusController;
|
||||||
|
|
||||||
struct SensorItem {
|
class SensorItem {
|
||||||
ModbusRegisterType register_type;
|
public:
|
||||||
SensorValueType sensor_value_type;
|
|
||||||
uint16_t start_address;
|
|
||||||
uint32_t bitmask;
|
|
||||||
uint8_t offset;
|
|
||||||
uint8_t register_count;
|
|
||||||
uint8_t skip_updates;
|
|
||||||
bool force_new_range{false};
|
|
||||||
|
|
||||||
virtual void parse_and_publish(const std::vector<uint8_t> &data) = 0;
|
virtual void parse_and_publish(const std::vector<uint8_t> &data) = 0;
|
||||||
|
|
||||||
|
void set_custom_data(const std::vector<uint8_t> &data) { custom_data = data; }
|
||||||
uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); }
|
uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); }
|
||||||
size_t virtual get_register_size() const {
|
size_t virtual get_register_size() const {
|
||||||
if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT)
|
if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT)
|
||||||
|
@ -266,10 +259,22 @@ struct SensorItem {
|
||||||
else
|
else
|
||||||
return register_count * 2;
|
return register_count * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModbusRegisterType register_type;
|
||||||
|
SensorValueType sensor_value_type;
|
||||||
|
uint16_t start_address;
|
||||||
|
uint32_t bitmask;
|
||||||
|
uint8_t offset;
|
||||||
|
uint8_t register_count;
|
||||||
|
uint8_t skip_updates;
|
||||||
|
std::vector<uint8_t> custom_data{};
|
||||||
|
bool force_new_range{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ModbusCommandItem {
|
class ModbusCommandItem {
|
||||||
|
public:
|
||||||
static const size_t MAX_PAYLOAD_BYTES = 240;
|
static const size_t MAX_PAYLOAD_BYTES = 240;
|
||||||
|
static const uint8_t MAX_SEND_REPEATS = 5;
|
||||||
ModbusController *modbusdevice;
|
ModbusController *modbusdevice;
|
||||||
uint16_t register_address;
|
uint16_t register_address;
|
||||||
uint16_t register_count;
|
uint16_t register_count;
|
||||||
|
@ -279,7 +284,9 @@ struct ModbusCommandItem {
|
||||||
on_data_func;
|
on_data_func;
|
||||||
std::vector<uint8_t> payload = {};
|
std::vector<uint8_t> payload = {};
|
||||||
bool send();
|
bool send();
|
||||||
|
// wrong commands (esp. custom commands) can block the send queue
|
||||||
|
// limit the number of repeats
|
||||||
|
uint8_t send_countdown{MAX_SEND_REPEATS};
|
||||||
/// factory methods
|
/// factory methods
|
||||||
/** Create modbus read command
|
/** Create modbus read command
|
||||||
* Function code 02-04
|
* Function code 02-04
|
||||||
|
@ -392,6 +399,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
|
||||||
protected:
|
protected:
|
||||||
/// parse sensormap_ and create range of sequential addresses
|
/// parse sensormap_ and create range of sequential addresses
|
||||||
size_t create_register_ranges_();
|
size_t create_register_ranges_();
|
||||||
|
// find register in sensormap. Returns iterator with all registers having the same start address
|
||||||
|
std::map<uint64_t, SensorItem *>::iterator find_register_(ModbusRegisterType register_type, uint16_t start_address);
|
||||||
/// submit the read command for the address range to the send queue
|
/// submit the read command for the address range to the send queue
|
||||||
void update_range_(RegisterRange &r);
|
void update_range_(RegisterRange &r);
|
||||||
/// parse incoming modbus data
|
/// parse incoming modbus data
|
||||||
|
|
|
@ -4,29 +4,26 @@ from esphome.components import number
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ADDRESS,
|
CONF_ADDRESS,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_LAMBDA,
|
|
||||||
CONF_MAX_VALUE,
|
CONF_MAX_VALUE,
|
||||||
CONF_MIN_VALUE,
|
CONF_MIN_VALUE,
|
||||||
CONF_MULTIPLY,
|
CONF_MULTIPLY,
|
||||||
CONF_OFFSET,
|
|
||||||
CONF_STEP,
|
CONF_STEP,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .. import (
|
from .. import (
|
||||||
|
add_modbus_base_properties,
|
||||||
modbus_controller_ns,
|
modbus_controller_ns,
|
||||||
ModbusController,
|
modbus_calc_properties,
|
||||||
SENSOR_VALUE_TYPE,
|
ModbusItemBaseSchema,
|
||||||
SensorItem,
|
SensorItem,
|
||||||
TYPE_REGISTER_MAP,
|
SENSOR_VALUE_TYPE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
CONF_BITMASK,
|
CONF_BITMASK,
|
||||||
CONF_BYTE_OFFSET,
|
CONF_CUSTOM_COMMAND,
|
||||||
CONF_FORCE_NEW_RANGE,
|
CONF_FORCE_NEW_RANGE,
|
||||||
CONF_MODBUS_CONTROLLER_ID,
|
CONF_MODBUS_CONTROLLER_ID,
|
||||||
CONF_REGISTER_COUNT,
|
|
||||||
CONF_SKIP_UPDATES,
|
CONF_SKIP_UPDATES,
|
||||||
CONF_VALUE_TYPE,
|
CONF_VALUE_TYPE,
|
||||||
CONF_WRITE_LAMBDA,
|
CONF_WRITE_LAMBDA,
|
||||||
|
@ -51,22 +48,21 @@ def validate_min_max(config):
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def validate_modbus_number(config):
|
||||||
|
if CONF_CUSTOM_COMMAND not in config and CONF_ADDRESS not in config:
|
||||||
|
raise cv.Invalid(
|
||||||
|
f" {CONF_ADDRESS} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used"
|
||||||
|
)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
number.NUMBER_SCHEMA.extend(
|
number.NUMBER_SCHEMA.extend(ModbusItemBaseSchema)
|
||||||
|
.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(ModbusNumber),
|
cv.GenerateID(): cv.declare_id(ModbusNumber),
|
||||||
cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
|
|
||||||
cv.Required(CONF_ADDRESS): cv.positive_int,
|
|
||||||
cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
|
|
||||||
cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
|
|
||||||
cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t,
|
|
||||||
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
|
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
|
||||||
cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
|
|
||||||
cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
|
|
||||||
cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
|
|
||||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
|
||||||
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
||||||
cv.GenerateID(): cv.declare_id(ModbusNumber),
|
|
||||||
# 24 bits are the maximum value for fp32 before precison is lost
|
# 24 bits are the maximum value for fp32 before precison is lost
|
||||||
# 0x00FFFFFF = 16777215
|
# 0x00FFFFFF = 16777215
|
||||||
cv.Optional(CONF_MAX_VALUE, default=16777215.0): cv.float_,
|
cv.Optional(CONF_MAX_VALUE, default=16777215.0): cv.float_,
|
||||||
|
@ -74,22 +70,15 @@ CONFIG_SCHEMA = cv.All(
|
||||||
cv.Optional(CONF_STEP, default=1): cv.positive_float,
|
cv.Optional(CONF_STEP, default=1): cv.positive_float,
|
||||||
cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_,
|
cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_,
|
||||||
}
|
}
|
||||||
).extend(cv.polling_component_schema("60s")),
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s")),
|
||||||
validate_min_max,
|
validate_min_max,
|
||||||
|
validate_modbus_number,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
byte_offset = 0
|
byte_offset, reg_count = modbus_calc_properties(config)
|
||||||
if CONF_OFFSET in config:
|
|
||||||
byte_offset = config[CONF_OFFSET]
|
|
||||||
# A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
|
|
||||||
if CONF_BYTE_OFFSET in config:
|
|
||||||
byte_offset = config[CONF_BYTE_OFFSET]
|
|
||||||
value_type = config[CONF_VALUE_TYPE]
|
|
||||||
reg_count = config[CONF_REGISTER_COUNT]
|
|
||||||
if reg_count == 0:
|
|
||||||
reg_count = TYPE_REGISTER_MAP[value_type]
|
|
||||||
var = cg.new_Pvariable(
|
var = cg.new_Pvariable(
|
||||||
config[CONF_ID],
|
config[CONF_ID],
|
||||||
config[CONF_ADDRESS],
|
config[CONF_ADDRESS],
|
||||||
|
@ -115,20 +104,7 @@ async def to_code(config):
|
||||||
|
|
||||||
cg.add(var.set_parent(parent))
|
cg.add(var.set_parent(parent))
|
||||||
cg.add(parent.add_sensor_item(var))
|
cg.add(parent.add_sensor_item(var))
|
||||||
if CONF_LAMBDA in config:
|
await add_modbus_base_properties(var, config, ModbusNumber)
|
||||||
template_ = await cg.process_lambda(
|
|
||||||
config[CONF_LAMBDA],
|
|
||||||
[
|
|
||||||
(ModbusNumber.operator("ptr"), "item"),
|
|
||||||
(cg.float_, "x"),
|
|
||||||
(
|
|
||||||
cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
|
|
||||||
"data",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
return_type=cg.optional.template(float),
|
|
||||||
)
|
|
||||||
cg.add(var.set_template(template_))
|
|
||||||
if CONF_WRITE_LAMBDA in config:
|
if CONF_WRITE_LAMBDA in config:
|
||||||
template_ = await cg.process_lambda(
|
template_ = await cg.process_lambda(
|
||||||
config[CONF_WRITE_LAMBDA],
|
config[CONF_WRITE_LAMBDA],
|
||||||
|
|
|
@ -6,24 +6,21 @@ from esphome.const import (
|
||||||
CONF_ADDRESS,
|
CONF_ADDRESS,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_MULTIPLY,
|
CONF_MULTIPLY,
|
||||||
CONF_OFFSET,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from .. import (
|
from .. import (
|
||||||
SensorItem,
|
|
||||||
modbus_controller_ns,
|
modbus_controller_ns,
|
||||||
ModbusController,
|
modbus_calc_properties,
|
||||||
TYPE_REGISTER_MAP,
|
validate_modbus_register,
|
||||||
|
ModbusItemBaseSchema,
|
||||||
|
SensorItem,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
CONF_BYTE_OFFSET,
|
|
||||||
CONF_MODBUS_CONTROLLER_ID,
|
CONF_MODBUS_CONTROLLER_ID,
|
||||||
CONF_REGISTER_COUNT,
|
|
||||||
CONF_VALUE_TYPE,
|
CONF_VALUE_TYPE,
|
||||||
CONF_WRITE_LAMBDA,
|
CONF_WRITE_LAMBDA,
|
||||||
)
|
)
|
||||||
from ..sensor import SENSOR_VALUE_TYPE
|
|
||||||
|
|
||||||
DEPENDENCIES = ["modbus_controller"]
|
DEPENDENCIES = ["modbus_controller"]
|
||||||
CODEOWNERS = ["@martgras"]
|
CODEOWNERS = ["@martgras"]
|
||||||
|
@ -34,38 +31,24 @@ ModbusOutput = modbus_controller_ns.class_(
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
output.FLOAT_OUTPUT_SCHEMA.extend(
|
output.FLOAT_OUTPUT_SCHEMA.extend(ModbusItemBaseSchema).extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
|
|
||||||
cv.GenerateID(): cv.declare_id(ModbusOutput),
|
cv.GenerateID(): cv.declare_id(ModbusOutput),
|
||||||
cv.Required(CONF_ADDRESS): cv.positive_int,
|
|
||||||
cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
|
|
||||||
cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
|
|
||||||
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
|
|
||||||
cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
|
|
||||||
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
||||||
cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_,
|
cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
validate_modbus_register,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
byte_offset = 0
|
byte_offset, reg_count = modbus_calc_properties(config)
|
||||||
if CONF_OFFSET in config:
|
|
||||||
byte_offset = config[CONF_OFFSET]
|
|
||||||
# A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
|
|
||||||
if CONF_BYTE_OFFSET in config:
|
|
||||||
byte_offset = config[CONF_BYTE_OFFSET]
|
|
||||||
value_type = config[CONF_VALUE_TYPE]
|
|
||||||
reg_count = config[CONF_REGISTER_COUNT]
|
|
||||||
if reg_count == 0:
|
|
||||||
reg_count = TYPE_REGISTER_MAP[value_type]
|
|
||||||
var = cg.new_Pvariable(
|
var = cg.new_Pvariable(
|
||||||
config[CONF_ID],
|
config[CONF_ID],
|
||||||
config[CONF_ADDRESS],
|
config[CONF_ADDRESS],
|
||||||
byte_offset,
|
byte_offset,
|
||||||
value_type,
|
config[CONF_VALUE_TYPE],
|
||||||
reg_count,
|
reg_count,
|
||||||
)
|
)
|
||||||
await output.register_output(var, config)
|
await output.register_output(var, config)
|
||||||
|
|
|
@ -2,18 +2,19 @@ from esphome.components import sensor
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
|
||||||
from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET
|
from esphome.const import CONF_ID, CONF_ADDRESS
|
||||||
from .. import (
|
from .. import (
|
||||||
SensorItem,
|
add_modbus_base_properties,
|
||||||
modbus_controller_ns,
|
modbus_controller_ns,
|
||||||
ModbusController,
|
modbus_calc_properties,
|
||||||
|
validate_modbus_register,
|
||||||
|
ModbusItemBaseSchema,
|
||||||
|
SensorItem,
|
||||||
MODBUS_REGISTER_TYPE,
|
MODBUS_REGISTER_TYPE,
|
||||||
SENSOR_VALUE_TYPE,
|
SENSOR_VALUE_TYPE,
|
||||||
TYPE_REGISTER_MAP,
|
|
||||||
)
|
)
|
||||||
from ..const import (
|
from ..const import (
|
||||||
CONF_BITMASK,
|
CONF_BITMASK,
|
||||||
CONF_BYTE_OFFSET,
|
|
||||||
CONF_FORCE_NEW_RANGE,
|
CONF_FORCE_NEW_RANGE,
|
||||||
CONF_MODBUS_CONTROLLER_ID,
|
CONF_MODBUS_CONTROLLER_ID,
|
||||||
CONF_REGISTER_COUNT,
|
CONF_REGISTER_COUNT,
|
||||||
|
@ -31,43 +32,30 @@ ModbusSensor = modbus_controller_ns.class_(
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
sensor.SENSOR_SCHEMA.extend(
|
sensor.SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(ModbusItemBaseSchema)
|
||||||
|
.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(ModbusSensor),
|
cv.GenerateID(): cv.declare_id(ModbusSensor),
|
||||||
cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
|
cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
|
||||||
cv.Required(CONF_ADDRESS): cv.positive_int,
|
|
||||||
cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
|
|
||||||
cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
|
|
||||||
cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
|
|
||||||
cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t,
|
|
||||||
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
|
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
|
||||||
cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
|
cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
|
||||||
cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
|
|
||||||
cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
|
|
||||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
),
|
||||||
|
validate_modbus_register,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
byte_offset = 0
|
byte_offset, reg_count = modbus_calc_properties(config)
|
||||||
if CONF_OFFSET in config:
|
|
||||||
byte_offset = config[CONF_OFFSET]
|
|
||||||
# A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
|
|
||||||
if CONF_BYTE_OFFSET in config:
|
|
||||||
byte_offset = config[CONF_BYTE_OFFSET]
|
|
||||||
value_type = config[CONF_VALUE_TYPE]
|
value_type = config[CONF_VALUE_TYPE]
|
||||||
reg_count = config[CONF_REGISTER_COUNT]
|
|
||||||
if reg_count == 0:
|
|
||||||
reg_count = TYPE_REGISTER_MAP[value_type]
|
|
||||||
var = cg.new_Pvariable(
|
var = cg.new_Pvariable(
|
||||||
config[CONF_ID],
|
config[CONF_ID],
|
||||||
config[CONF_REGISTER_TYPE],
|
config[CONF_REGISTER_TYPE],
|
||||||
config[CONF_ADDRESS],
|
config[CONF_ADDRESS],
|
||||||
byte_offset,
|
byte_offset,
|
||||||
config[CONF_BITMASK],
|
config[CONF_BITMASK],
|
||||||
config[CONF_VALUE_TYPE],
|
value_type,
|
||||||
reg_count,
|
reg_count,
|
||||||
config[CONF_SKIP_UPDATES],
|
config[CONF_SKIP_UPDATES],
|
||||||
config[CONF_FORCE_NEW_RANGE],
|
config[CONF_FORCE_NEW_RANGE],
|
||||||
|
@ -77,17 +65,4 @@ async def to_code(config):
|
||||||
|
|
||||||
paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
|
paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
|
||||||
cg.add(paren.add_sensor_item(var))
|
cg.add(paren.add_sensor_item(var))
|
||||||
if CONF_LAMBDA in config:
|
await add_modbus_base_properties(var, config, ModbusSensor)
|
||||||
template_ = await cg.process_lambda(
|
|
||||||
config[CONF_LAMBDA],
|
|
||||||
[
|
|
||||||
(ModbusSensor.operator("ptr"), "item"),
|
|
||||||
(cg.float_, "x"),
|
|
||||||
(
|
|
||||||
cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
|
|
||||||
"data",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
return_type=cg.optional.template(float),
|
|
||||||
)
|
|
||||||
cg.add(var.set_template(template_))
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ class ModbusSensor : public Component, public sensor::Sensor, public SensorItem
|
||||||
void parse_and_publish(const std::vector<uint8_t> &data) override;
|
void parse_and_publish(const std::vector<uint8_t> &data) override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
using transform_func_t = std::function<optional<float>(ModbusSensor *, float, const std::vector<uint8_t> &)>;
|
using transform_func_t = std::function<optional<float>(ModbusSensor *, float, const std::vector<uint8_t> &)>;
|
||||||
|
|
||||||
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
|
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -3,21 +3,25 @@ import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
|
||||||
|
|
||||||
from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET
|
from esphome.const import CONF_ID, CONF_ADDRESS
|
||||||
from .. import (
|
from .. import (
|
||||||
MODBUS_REGISTER_TYPE,
|
add_modbus_base_properties,
|
||||||
SensorItem,
|
|
||||||
modbus_controller_ns,
|
modbus_controller_ns,
|
||||||
ModbusController,
|
modbus_calc_properties,
|
||||||
|
validate_modbus_register,
|
||||||
|
ModbusItemBaseSchema,
|
||||||
|
SensorItem,
|
||||||
|
MODBUS_REGISTER_TYPE,
|
||||||
)
|
)
|
||||||
from ..const import (
|
from ..const import (
|
||||||
CONF_BITMASK,
|
CONF_BITMASK,
|
||||||
CONF_BYTE_OFFSET,
|
|
||||||
CONF_FORCE_NEW_RANGE,
|
CONF_FORCE_NEW_RANGE,
|
||||||
CONF_MODBUS_CONTROLLER_ID,
|
CONF_MODBUS_CONTROLLER_ID,
|
||||||
CONF_REGISTER_TYPE,
|
CONF_REGISTER_TYPE,
|
||||||
|
CONF_WRITE_LAMBDA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CONF_USE_WRITE_MULTIPLE = "use_write_multiple"
|
||||||
DEPENDENCIES = ["modbus_controller"]
|
DEPENDENCIES = ["modbus_controller"]
|
||||||
CODEOWNERS = ["@martgras"]
|
CODEOWNERS = ["@martgras"]
|
||||||
|
|
||||||
|
@ -26,31 +30,23 @@ ModbusSwitch = modbus_controller_ns.class_(
|
||||||
"ModbusSwitch", cg.Component, switch.Switch, SensorItem
|
"ModbusSwitch", cg.Component, switch.Switch, SensorItem
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
switch.SWITCH_SCHEMA.extend(
|
switch.SWITCH_SCHEMA.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(ModbusItemBaseSchema)
|
||||||
|
.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(ModbusSwitch),
|
cv.GenerateID(): cv.declare_id(ModbusSwitch),
|
||||||
cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
|
cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
|
||||||
cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
|
cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
|
||||||
cv.Required(CONF_ADDRESS): cv.positive_int,
|
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
||||||
cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
|
|
||||||
cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
|
|
||||||
cv.Optional(CONF_BITMASK, default=0x1): cv.hex_uint32_t,
|
|
||||||
cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
|
|
||||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
),
|
||||||
|
validate_modbus_register,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
byte_offset = 0
|
byte_offset, _ = modbus_calc_properties(config)
|
||||||
if CONF_OFFSET in config:
|
|
||||||
byte_offset = config[CONF_OFFSET]
|
|
||||||
# A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
|
|
||||||
if CONF_BYTE_OFFSET in config:
|
|
||||||
byte_offset = config[CONF_BYTE_OFFSET]
|
|
||||||
var = cg.new_Pvariable(
|
var = cg.new_Pvariable(
|
||||||
config[CONF_ID],
|
config[CONF_ID],
|
||||||
config[CONF_REGISTER_TYPE],
|
config[CONF_REGISTER_TYPE],
|
||||||
|
@ -63,19 +59,18 @@ async def to_code(config):
|
||||||
await switch.register_switch(var, config)
|
await switch.register_switch(var, config)
|
||||||
|
|
||||||
paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
|
paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
|
||||||
cg.add(paren.add_sensor_item(var))
|
|
||||||
cg.add(var.set_parent(paren))
|
cg.add(var.set_parent(paren))
|
||||||
if CONF_LAMBDA in config:
|
cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE]))
|
||||||
publish_template_ = await cg.process_lambda(
|
cg.add(paren.add_sensor_item(var))
|
||||||
config[CONF_LAMBDA],
|
if CONF_WRITE_LAMBDA in config:
|
||||||
|
template_ = await cg.process_lambda(
|
||||||
|
config[CONF_WRITE_LAMBDA],
|
||||||
[
|
[
|
||||||
(ModbusSwitch.operator("ptr"), "item"),
|
(ModbusSwitch.operator("ptr"), "item"),
|
||||||
(bool, "x"),
|
(cg.bool_, "x"),
|
||||||
(
|
(cg.std_vector.template(cg.uint8).operator("ref"), "payload"),
|
||||||
cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
|
|
||||||
"data",
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
return_type=cg.optional.template(bool),
|
return_type=cg.optional.template(bool),
|
||||||
)
|
)
|
||||||
cg.add(var.set_template(publish_template_))
|
cg.add(var.set_write_template(template_))
|
||||||
|
await add_modbus_base_properties(var, config, ModbusSwitch, bool, bool)
|
||||||
|
|
|
@ -45,22 +45,50 @@ void ModbusSwitch::parse_and_publish(const std::vector<uint8_t> &data) {
|
||||||
void ModbusSwitch::write_state(bool state) {
|
void ModbusSwitch::write_state(bool state) {
|
||||||
// This will be called every time the user requests a state change.
|
// This will be called every time the user requests a state change.
|
||||||
ModbusCommandItem cmd;
|
ModbusCommandItem cmd;
|
||||||
ESP_LOGV(TAG, "write_state '%s': new value = %s type = %d address = %X offset = %x", this->get_name().c_str(),
|
std::vector<uint8_t> data;
|
||||||
ONOFF(state), (int) this->register_type, this->start_address, this->offset);
|
// Is there are lambda configured?
|
||||||
switch (this->register_type) {
|
if (this->write_transform_func_.has_value()) {
|
||||||
case ModbusRegisterType::COIL:
|
// data is passed by reference
|
||||||
|
// the lambda can fill the empty vector directly
|
||||||
|
// in that case the return value is ignored
|
||||||
|
auto val = (*this->write_transform_func_)(this, state, data);
|
||||||
|
if (val.has_value()) {
|
||||||
|
ESP_LOGV(TAG, "Value overwritten by lambda");
|
||||||
|
state = val.value();
|
||||||
|
} else {
|
||||||
|
ESP_LOGV(TAG, "Communication handled by lambda - exiting control");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!data.empty()) {
|
||||||
|
ESP_LOGV(TAG, "Modbus Switch write raw: %s", hexencode(data).c_str());
|
||||||
|
cmd = ModbusCommandItem::create_custom_command(
|
||||||
|
this->parent_, data,
|
||||||
|
[this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
|
||||||
|
this->parent_->on_write_register_response(cmd.register_type, this->start_address, data);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ESP_LOGV(TAG, "write_state '%s': new value = %s type = %d address = %X offset = %x", this->get_name().c_str(),
|
||||||
|
ONOFF(state), (int) this->register_type, this->start_address, this->offset);
|
||||||
|
if (this->register_type == ModbusRegisterType::COIL) {
|
||||||
// offset for coil and discrete inputs is the coil/register number not bytes
|
// offset for coil and discrete inputs is the coil/register number not bytes
|
||||||
cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state);
|
if (this->use_write_multiple_) {
|
||||||
break;
|
std::vector<bool> states{state};
|
||||||
case ModbusRegisterType::DISCRETE_INPUT:
|
cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states);
|
||||||
cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, state);
|
} else {
|
||||||
break;
|
cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state);
|
||||||
|
}
|
||||||
default:
|
} else {
|
||||||
// since offset is in bytes and a register is 16 bits we get the start by adding offset/2
|
// since offset is in bytes and a register is 16 bits we get the start by adding offset/2
|
||||||
cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2,
|
if (this->use_write_multiple_) {
|
||||||
state ? 0xFFFF & this->bitmask : 0);
|
std::vector<uint16_t> bool_states(1, state ? (0xFFFF & this->bitmask) : 0);
|
||||||
break;
|
cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, 1,
|
||||||
|
bool_states);
|
||||||
|
} else {
|
||||||
|
cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2,
|
||||||
|
state ? 0xFFFF & this->bitmask : 0u);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this->parent_->queue_command(cmd);
|
this->parent_->queue_command(cmd);
|
||||||
publish_state(state);
|
publish_state(state);
|
||||||
|
|
|
@ -33,11 +33,16 @@ class ModbusSwitch : public Component, public switch_::Switch, public SensorItem
|
||||||
void set_parent(ModbusController *parent) { this->parent_ = parent; }
|
void set_parent(ModbusController *parent) { this->parent_ = parent; }
|
||||||
|
|
||||||
using transform_func_t = std::function<optional<bool>(ModbusSwitch *, bool, const std::vector<uint8_t> &)>;
|
using transform_func_t = std::function<optional<bool>(ModbusSwitch *, bool, const std::vector<uint8_t> &)>;
|
||||||
|
using write_transform_func_t = std::function<optional<bool>(ModbusSwitch *, bool, std::vector<uint8_t> &)>;
|
||||||
void set_template(transform_func_t &&f) { this->publish_transform_func_ = f; }
|
void set_template(transform_func_t &&f) { this->publish_transform_func_ = f; }
|
||||||
|
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
|
||||||
|
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ModbusController *parent_;
|
ModbusController *parent_;
|
||||||
|
bool use_write_multiple_;
|
||||||
optional<transform_func_t> publish_transform_func_{nullopt};
|
optional<transform_func_t> publish_transform_func_{nullopt};
|
||||||
|
optional<write_transform_func_t> write_transform_func_{nullopt};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace modbus_controller
|
} // namespace modbus_controller
|
||||||
|
|
|
@ -3,15 +3,17 @@ import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
|
||||||
|
|
||||||
from esphome.const import CONF_ID, CONF_ADDRESS, CONF_LAMBDA, CONF_OFFSET
|
from esphome.const import CONF_ADDRESS, CONF_ID
|
||||||
from .. import (
|
from .. import (
|
||||||
SensorItem,
|
add_modbus_base_properties,
|
||||||
modbus_controller_ns,
|
modbus_controller_ns,
|
||||||
ModbusController,
|
modbus_calc_properties,
|
||||||
|
validate_modbus_register,
|
||||||
|
ModbusItemBaseSchema,
|
||||||
|
SensorItem,
|
||||||
MODBUS_REGISTER_TYPE,
|
MODBUS_REGISTER_TYPE,
|
||||||
)
|
)
|
||||||
from ..const import (
|
from ..const import (
|
||||||
CONF_BYTE_OFFSET,
|
|
||||||
CONF_FORCE_NEW_RANGE,
|
CONF_FORCE_NEW_RANGE,
|
||||||
CONF_MODBUS_CONTROLLER_ID,
|
CONF_MODBUS_CONTROLLER_ID,
|
||||||
CONF_REGISTER_COUNT,
|
CONF_REGISTER_COUNT,
|
||||||
|
@ -38,32 +40,23 @@ RAW_ENCODING = {
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
text_sensor.TEXT_SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(ModbusItemBaseSchema)
|
||||||
|
.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(ModbusTextSensor),
|
cv.GenerateID(): cv.declare_id(ModbusTextSensor),
|
||||||
cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
|
cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
|
||||||
cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
|
|
||||||
cv.Required(CONF_ADDRESS): cv.positive_int,
|
|
||||||
cv.Optional(CONF_OFFSET, default=0): cv.positive_int,
|
|
||||||
cv.Optional(CONF_BYTE_OFFSET): cv.positive_int,
|
|
||||||
cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
|
cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
|
||||||
cv.Optional(CONF_RESPONSE_SIZE, default=2): cv.positive_int,
|
cv.Optional(CONF_RESPONSE_SIZE, default=2): cv.positive_int,
|
||||||
cv.Optional(CONF_RAW_ENCODE, default="NONE"): cv.enum(RAW_ENCODING),
|
cv.Optional(CONF_RAW_ENCODE, default="NONE"): cv.enum(RAW_ENCODING),
|
||||||
cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
|
|
||||||
cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
|
|
||||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
),
|
||||||
|
validate_modbus_register,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
byte_offset = 0
|
byte_offset, reg_count = modbus_calc_properties(config)
|
||||||
if CONF_OFFSET in config:
|
|
||||||
byte_offset = config[CONF_OFFSET]
|
|
||||||
# A CONF_BYTE_OFFSET setting overrides CONF_OFFSET
|
|
||||||
if CONF_BYTE_OFFSET in config:
|
|
||||||
byte_offset = config[CONF_BYTE_OFFSET]
|
|
||||||
response_size = config[CONF_RESPONSE_SIZE]
|
response_size = config[CONF_RESPONSE_SIZE]
|
||||||
reg_count = config[CONF_REGISTER_COUNT]
|
reg_count = config[CONF_REGISTER_COUNT]
|
||||||
if reg_count == 0:
|
if reg_count == 0:
|
||||||
|
@ -85,17 +78,6 @@ async def to_code(config):
|
||||||
|
|
||||||
paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
|
paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
|
||||||
cg.add(paren.add_sensor_item(var))
|
cg.add(paren.add_sensor_item(var))
|
||||||
if CONF_LAMBDA in config:
|
await add_modbus_base_properties(
|
||||||
template_ = await cg.process_lambda(
|
var, config, ModbusTextSensor, cg.std_string, cg.std_string
|
||||||
config[CONF_LAMBDA],
|
)
|
||||||
[
|
|
||||||
(ModbusTextSensor.operator("ptr"), "item"),
|
|
||||||
(cg.std_string.operator("const").operator("ref"), "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_))
|
|
||||||
|
|
Loading…
Reference in a new issue