mirror of
https://github.com/esphome/esphome.git
synced 2025-10-10 22:03:46 +01:00
[modbus_controller] courtesy response (#10027)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
@@ -66,7 +66,10 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) {
|
|||||||
uint8_t data_offset = 3;
|
uint8_t data_offset = 3;
|
||||||
|
|
||||||
// Per https://modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf Ch 5 User-Defined function codes
|
// Per https://modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf Ch 5 User-Defined function codes
|
||||||
if (((function_code >= 65) && (function_code <= 72)) || ((function_code >= 100) && (function_code <= 110))) {
|
if (((function_code >= FUNCTION_CODE_USER_DEFINED_SPACE_1_INIT) &&
|
||||||
|
(function_code <= FUNCTION_CODE_USER_DEFINED_SPACE_1_END)) ||
|
||||||
|
((function_code >= FUNCTION_CODE_USER_DEFINED_SPACE_2_INIT) &&
|
||||||
|
(function_code <= FUNCTION_CODE_USER_DEFINED_SPACE_2_END))) {
|
||||||
// Handle user-defined function, since we don't know how big this ought to be,
|
// Handle user-defined function, since we don't know how big this ought to be,
|
||||||
// ideally we should delegate the entire length detection to whatever handler is
|
// ideally we should delegate the entire length detection to whatever handler is
|
||||||
// installed, but wait, there is the CRC, and if we get a hit there is a good
|
// installed, but wait, there is the CRC, and if we get a hit there is a good
|
||||||
@@ -91,10 +94,14 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) {
|
|||||||
} else {
|
} else {
|
||||||
// data starts at 2 and length is 4 for read registers commands
|
// data starts at 2 and length is 4 for read registers commands
|
||||||
if (this->role == ModbusRole::SERVER) {
|
if (this->role == ModbusRole::SERVER) {
|
||||||
if (function_code == 0x1 || function_code == 0x3 || function_code == 0x4 || function_code == 0x6) {
|
if (function_code == ModbusFunctionCode::READ_COILS ||
|
||||||
|
function_code == ModbusFunctionCode::READ_DISCRETE_INPUTS ||
|
||||||
|
function_code == ModbusFunctionCode::READ_HOLDING_REGISTERS ||
|
||||||
|
function_code == ModbusFunctionCode::READ_INPUT_REGISTERS ||
|
||||||
|
function_code == ModbusFunctionCode::WRITE_SINGLE_REGISTER) {
|
||||||
data_offset = 2;
|
data_offset = 2;
|
||||||
data_len = 4;
|
data_len = 4;
|
||||||
} else if (function_code == 0x10) {
|
} else if (function_code == ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS) {
|
||||||
if (at < 6) {
|
if (at < 6) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -104,7 +111,10 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// the response for write command mirrors the requests and data starts at offset 2 instead of 3 for read commands
|
// the response for write command mirrors the requests and data starts at offset 2 instead of 3 for read commands
|
||||||
if (function_code == 0x5 || function_code == 0x06 || function_code == 0xF || function_code == 0x10) {
|
if (function_code == ModbusFunctionCode::WRITE_SINGLE_COIL ||
|
||||||
|
function_code == ModbusFunctionCode::WRITE_SINGLE_REGISTER ||
|
||||||
|
function_code == ModbusFunctionCode::WRITE_MULTIPLE_COILS ||
|
||||||
|
function_code == ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS) {
|
||||||
data_offset = 2;
|
data_offset = 2;
|
||||||
data_len = 4;
|
data_len = 4;
|
||||||
}
|
}
|
||||||
@@ -112,7 +122,7 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) {
|
|||||||
|
|
||||||
// Error ( msb indicates error )
|
// Error ( msb indicates error )
|
||||||
// response format: Byte[0] = device address, Byte[1] function code | 0x80 , Byte[2] exception code, Byte[3-4] crc
|
// response format: Byte[0] = device address, Byte[1] function code | 0x80 , Byte[2] exception code, Byte[3-4] crc
|
||||||
if ((function_code & 0x80) == 0x80) {
|
if ((function_code & FUNCTION_CODE_EXCEPTION_MASK) == FUNCTION_CODE_EXCEPTION_MASK) {
|
||||||
data_offset = 2;
|
data_offset = 2;
|
||||||
data_len = 1;
|
data_len = 1;
|
||||||
}
|
}
|
||||||
@@ -143,10 +153,10 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) {
|
|||||||
if (device->address_ == address) {
|
if (device->address_ == address) {
|
||||||
found = true;
|
found = true;
|
||||||
// Is it an error response?
|
// Is it an error response?
|
||||||
if ((function_code & 0x80) == 0x80) {
|
if ((function_code & FUNCTION_CODE_EXCEPTION_MASK) == FUNCTION_CODE_EXCEPTION_MASK) {
|
||||||
ESP_LOGD(TAG, "Modbus error function code: 0x%X exception: %d", function_code, raw[2]);
|
ESP_LOGD(TAG, "Modbus error function code: 0x%X exception: %d", function_code, raw[2]);
|
||||||
if (waiting_for_response != 0) {
|
if (waiting_for_response != 0) {
|
||||||
device->on_modbus_error(function_code & 0x7F, raw[2]);
|
device->on_modbus_error(function_code & FUNCTION_CODE_MASK, raw[2]);
|
||||||
} else {
|
} else {
|
||||||
// Ignore modbus exception not related to a pending command
|
// Ignore modbus exception not related to a pending command
|
||||||
ESP_LOGD(TAG, "Ignoring Modbus error - not expecting a response");
|
ESP_LOGD(TAG, "Ignoring Modbus error - not expecting a response");
|
||||||
@@ -154,12 +164,14 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (this->role == ModbusRole::SERVER) {
|
if (this->role == ModbusRole::SERVER) {
|
||||||
if (function_code == 0x3 || function_code == 0x4) {
|
if (function_code == ModbusFunctionCode::READ_HOLDING_REGISTERS ||
|
||||||
|
function_code == ModbusFunctionCode::READ_INPUT_REGISTERS) {
|
||||||
device->on_modbus_read_registers(function_code, uint16_t(data[1]) | (uint16_t(data[0]) << 8),
|
device->on_modbus_read_registers(function_code, uint16_t(data[1]) | (uint16_t(data[0]) << 8),
|
||||||
uint16_t(data[3]) | (uint16_t(data[2]) << 8));
|
uint16_t(data[3]) | (uint16_t(data[2]) << 8));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (function_code == 0x6 || function_code == 0x10) {
|
if (function_code == ModbusFunctionCode::WRITE_SINGLE_REGISTER ||
|
||||||
|
function_code == ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS) {
|
||||||
device->on_modbus_write_registers(function_code, data);
|
device->on_modbus_write_registers(function_code, data);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -199,7 +211,7 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address
|
|||||||
|
|
||||||
// Only check max number of registers for standard function codes
|
// Only check max number of registers for standard function codes
|
||||||
// Some devices use non standard codes like 0x43
|
// Some devices use non standard codes like 0x43
|
||||||
if (number_of_entities > MAX_VALUES && function_code <= 0x10) {
|
if (number_of_entities > MAX_VALUES && function_code <= ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS) {
|
||||||
ESP_LOGE(TAG, "send too many values %d max=%zu", number_of_entities, MAX_VALUES);
|
ESP_LOGE(TAG, "send too many values %d max=%zu", number_of_entities, MAX_VALUES);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -210,15 +222,17 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address
|
|||||||
if (this->role == ModbusRole::CLIENT) {
|
if (this->role == ModbusRole::CLIENT) {
|
||||||
data.push_back(start_address >> 8);
|
data.push_back(start_address >> 8);
|
||||||
data.push_back(start_address >> 0);
|
data.push_back(start_address >> 0);
|
||||||
if (function_code != 0x5 && function_code != 0x6) {
|
if (function_code != ModbusFunctionCode::WRITE_SINGLE_COIL &&
|
||||||
|
function_code != ModbusFunctionCode::WRITE_SINGLE_REGISTER) {
|
||||||
data.push_back(number_of_entities >> 8);
|
data.push_back(number_of_entities >> 8);
|
||||||
data.push_back(number_of_entities >> 0);
|
data.push_back(number_of_entities >> 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload != nullptr) {
|
if (payload != nullptr) {
|
||||||
if (this->role == ModbusRole::SERVER || function_code == 0xF || function_code == 0x10) { // Write multiple
|
if (this->role == ModbusRole::SERVER || function_code == ModbusFunctionCode::WRITE_MULTIPLE_COILS ||
|
||||||
data.push_back(payload_len); // Byte count is required for write
|
function_code == ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS) { // Write multiple
|
||||||
|
data.push_back(payload_len); // Byte count is required for write
|
||||||
} else {
|
} else {
|
||||||
payload_len = 2; // Write single register or coil
|
payload_len = 2; // Write single register or coil
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,8 @@
|
|||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/uart/uart.h"
|
#include "esphome/components/uart/uart.h"
|
||||||
|
|
||||||
|
#include "esphome/components/modbus/modbus_definitions.h"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -65,12 +67,12 @@ class ModbusDevice {
|
|||||||
this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload);
|
this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload);
|
||||||
}
|
}
|
||||||
void send_raw(const std::vector<uint8_t> &payload) { this->parent_->send_raw(payload); }
|
void send_raw(const std::vector<uint8_t> &payload) { this->parent_->send_raw(payload); }
|
||||||
void send_error(uint8_t function_code, uint8_t exception_code) {
|
void send_error(uint8_t function_code, ModbusExceptionCode exception_code) {
|
||||||
std::vector<uint8_t> error_response;
|
std::vector<uint8_t> error_response;
|
||||||
error_response.reserve(3);
|
error_response.reserve(3);
|
||||||
error_response.push_back(this->address_);
|
error_response.push_back(this->address_);
|
||||||
error_response.push_back(function_code | 0x80);
|
error_response.push_back(function_code | FUNCTION_CODE_EXCEPTION_MASK);
|
||||||
error_response.push_back(exception_code);
|
error_response.push_back(static_cast<uint8_t>(exception_code));
|
||||||
this->send_raw(error_response);
|
this->send_raw(error_response);
|
||||||
}
|
}
|
||||||
// If more than one device is connected block sending a new command before a response is received
|
// If more than one device is connected block sending a new command before a response is received
|
||||||
|
86
esphome/components/modbus/modbus_definitions.h
Normal file
86
esphome/components/modbus/modbus_definitions.h
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace modbus {
|
||||||
|
|
||||||
|
/// Modbus definitions from specs:
|
||||||
|
/// https://modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf
|
||||||
|
// 5 Function Code Categories
|
||||||
|
const uint8_t FUNCTION_CODE_USER_DEFINED_SPACE_1_INIT = 65; // 0x41
|
||||||
|
const uint8_t FUNCTION_CODE_USER_DEFINED_SPACE_1_END = 72; // 0x48
|
||||||
|
|
||||||
|
const uint8_t FUNCTION_CODE_USER_DEFINED_SPACE_2_INIT = 100; // 0x64
|
||||||
|
const uint8_t FUNCTION_CODE_USER_DEFINED_SPACE_2_END = 110; // 0x6E
|
||||||
|
|
||||||
|
enum class ModbusFunctionCode : uint8_t {
|
||||||
|
CUSTOM = 0x00,
|
||||||
|
READ_COILS = 0x01,
|
||||||
|
READ_DISCRETE_INPUTS = 0x02,
|
||||||
|
READ_HOLDING_REGISTERS = 0x03,
|
||||||
|
READ_INPUT_REGISTERS = 0x04,
|
||||||
|
WRITE_SINGLE_COIL = 0x05,
|
||||||
|
WRITE_SINGLE_REGISTER = 0x06,
|
||||||
|
READ_EXCEPTION_STATUS = 0x07, // not implemented
|
||||||
|
DIAGNOSTICS = 0x08, // not implemented
|
||||||
|
GET_COMM_EVENT_COUNTER = 0x0B, // not implemented
|
||||||
|
GET_COMM_EVENT_LOG = 0x0C, // not implemented
|
||||||
|
WRITE_MULTIPLE_COILS = 0x0F,
|
||||||
|
WRITE_MULTIPLE_REGISTERS = 0x10,
|
||||||
|
REPORT_SERVER_ID = 0x11, // not implemented
|
||||||
|
READ_FILE_RECORD = 0x14, // not implemented
|
||||||
|
WRITE_FILE_RECORD = 0x15, // not implemented
|
||||||
|
MASK_WRITE_REGISTER = 0x16, // not implemented
|
||||||
|
READ_WRITE_MULTIPLE_REGISTERS = 0x17, // not implemented
|
||||||
|
READ_FIFO_QUEUE = 0x18, // not implemented
|
||||||
|
};
|
||||||
|
|
||||||
|
/*Allow comparison operators between ModbusFunctionCode and uint8_t*/
|
||||||
|
inline bool operator==(ModbusFunctionCode lhs, uint8_t rhs) { return static_cast<uint8_t>(lhs) == rhs; }
|
||||||
|
inline bool operator==(uint8_t lhs, ModbusFunctionCode rhs) { return lhs == static_cast<uint8_t>(rhs); }
|
||||||
|
inline bool operator!=(ModbusFunctionCode lhs, uint8_t rhs) { return !(static_cast<uint8_t>(lhs) == rhs); }
|
||||||
|
inline bool operator!=(uint8_t lhs, ModbusFunctionCode rhs) { return !(lhs == static_cast<uint8_t>(rhs)); }
|
||||||
|
inline bool operator<(ModbusFunctionCode lhs, uint8_t rhs) { return static_cast<uint8_t>(lhs) < rhs; }
|
||||||
|
inline bool operator<(uint8_t lhs, ModbusFunctionCode rhs) { return lhs < static_cast<uint8_t>(rhs); }
|
||||||
|
inline bool operator<=(ModbusFunctionCode lhs, uint8_t rhs) { return static_cast<uint8_t>(lhs) <= rhs; }
|
||||||
|
inline bool operator<=(uint8_t lhs, ModbusFunctionCode rhs) { return lhs <= static_cast<uint8_t>(rhs); }
|
||||||
|
inline bool operator>(ModbusFunctionCode lhs, uint8_t rhs) { return static_cast<uint8_t>(lhs) > rhs; }
|
||||||
|
inline bool operator>(uint8_t lhs, ModbusFunctionCode rhs) { return lhs > static_cast<uint8_t>(rhs); }
|
||||||
|
inline bool operator>=(ModbusFunctionCode lhs, uint8_t rhs) { return static_cast<uint8_t>(lhs) >= rhs; }
|
||||||
|
inline bool operator>=(uint8_t lhs, ModbusFunctionCode rhs) { return lhs >= static_cast<uint8_t>(rhs); }
|
||||||
|
|
||||||
|
// 4.3 MODBUS Data model
|
||||||
|
enum class ModbusRegisterType : uint8_t {
|
||||||
|
CUSTOM = 0x00,
|
||||||
|
COIL = 0x01,
|
||||||
|
DISCRETE_INPUT = 0x02,
|
||||||
|
HOLDING = 0x03,
|
||||||
|
READ = 0x04,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 7 MODBUS Exception Responses:
|
||||||
|
const uint8_t FUNCTION_CODE_MASK = 0x7F;
|
||||||
|
const uint8_t FUNCTION_CODE_EXCEPTION_MASK = 0x80;
|
||||||
|
|
||||||
|
enum class ModbusExceptionCode : uint8_t {
|
||||||
|
ILLEGAL_FUNCTION = 0x01,
|
||||||
|
ILLEGAL_DATA_ADDRESS = 0x02,
|
||||||
|
ILLEGAL_DATA_VALUE = 0x03,
|
||||||
|
SERVICE_DEVICE_FAILURE = 0x04,
|
||||||
|
ACKNOWLEDGE = 0x05,
|
||||||
|
SERVER_DEVICE_BUSY = 0x06,
|
||||||
|
MEMORY_PARITY_ERROR = 0x08,
|
||||||
|
GATEWAY_PATH_UNAVAILABLE = 0x0A,
|
||||||
|
GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 0x0B,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 6.12 16 (0x10) Write Multiple registers:
|
||||||
|
const uint8_t MAX_NUM_OF_REGISTERS_TO_WRITE = 123; // 0x7B
|
||||||
|
|
||||||
|
// 6.3 03 (0x03) Read Holding Registers
|
||||||
|
// 6.4 04 (0x04) Read Input Registers
|
||||||
|
const uint8_t MAX_NUM_OF_REGISTERS_TO_READ = 125; // 0x7D
|
||||||
|
/// End of Modbus definitions
|
||||||
|
} // namespace modbus
|
||||||
|
} // namespace esphome
|
@@ -20,6 +20,7 @@ from .const import (
|
|||||||
CONF_BYTE_OFFSET,
|
CONF_BYTE_OFFSET,
|
||||||
CONF_COMMAND_THROTTLE,
|
CONF_COMMAND_THROTTLE,
|
||||||
CONF_CUSTOM_COMMAND,
|
CONF_CUSTOM_COMMAND,
|
||||||
|
CONF_ENABLED,
|
||||||
CONF_FORCE_NEW_RANGE,
|
CONF_FORCE_NEW_RANGE,
|
||||||
CONF_MAX_CMD_RETRIES,
|
CONF_MAX_CMD_RETRIES,
|
||||||
CONF_MODBUS_CONTROLLER_ID,
|
CONF_MODBUS_CONTROLLER_ID,
|
||||||
@@ -28,8 +29,11 @@ from .const import (
|
|||||||
CONF_ON_OFFLINE,
|
CONF_ON_OFFLINE,
|
||||||
CONF_ON_ONLINE,
|
CONF_ON_ONLINE,
|
||||||
CONF_REGISTER_COUNT,
|
CONF_REGISTER_COUNT,
|
||||||
|
CONF_REGISTER_LAST_ADDRESS,
|
||||||
CONF_REGISTER_TYPE,
|
CONF_REGISTER_TYPE,
|
||||||
|
CONF_REGISTER_VALUE,
|
||||||
CONF_RESPONSE_SIZE,
|
CONF_RESPONSE_SIZE,
|
||||||
|
CONF_SERVER_COURTESY_RESPONSE,
|
||||||
CONF_SKIP_UPDATES,
|
CONF_SKIP_UPDATES,
|
||||||
CONF_VALUE_TYPE,
|
CONF_VALUE_TYPE,
|
||||||
)
|
)
|
||||||
@@ -49,6 +53,7 @@ ModbusController = modbus_controller_ns.class_(
|
|||||||
)
|
)
|
||||||
|
|
||||||
SensorItem = modbus_controller_ns.struct("SensorItem")
|
SensorItem = modbus_controller_ns.struct("SensorItem")
|
||||||
|
ServerCourtesyResponse = modbus_controller_ns.struct("ServerCourtesyResponse")
|
||||||
ServerRegister = modbus_controller_ns.struct("ServerRegister")
|
ServerRegister = modbus_controller_ns.struct("ServerRegister")
|
||||||
|
|
||||||
ModbusFunctionCode_ns = modbus_controller_ns.namespace("ModbusFunctionCode")
|
ModbusFunctionCode_ns = modbus_controller_ns.namespace("ModbusFunctionCode")
|
||||||
@@ -143,6 +148,14 @@ ModbusOfflineTrigger = modbus_controller_ns.class_(
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SERVER_COURTESY_RESPONSE_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_ENABLED, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_REGISTER_LAST_ADDRESS, default=0xFFFF): cv.hex_uint16_t,
|
||||||
|
cv.Optional(CONF_REGISTER_VALUE, default=0): cv.hex_uint16_t,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
ModbusServerRegisterSchema = cv.Schema(
|
ModbusServerRegisterSchema = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(ServerRegister),
|
cv.GenerateID(): cv.declare_id(ServerRegister),
|
||||||
@@ -162,6 +175,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_COMMAND_THROTTLE, default="0ms"
|
CONF_COMMAND_THROTTLE, default="0ms"
|
||||||
): cv.positive_time_period_milliseconds,
|
): cv.positive_time_period_milliseconds,
|
||||||
|
cv.Optional(CONF_SERVER_COURTESY_RESPONSE): SERVER_COURTESY_RESPONSE_SCHEMA,
|
||||||
cv.Optional(CONF_MAX_CMD_RETRIES, default=4): cv.positive_int,
|
cv.Optional(CONF_MAX_CMD_RETRIES, default=4): cv.positive_int,
|
||||||
cv.Optional(CONF_OFFLINE_SKIP_UPDATES, default=0): cv.positive_int,
|
cv.Optional(CONF_OFFLINE_SKIP_UPDATES, default=0): cv.positive_int,
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
@@ -232,7 +246,7 @@ def validate_modbus_register(config):
|
|||||||
|
|
||||||
|
|
||||||
def _final_validate(config):
|
def _final_validate(config):
|
||||||
if CONF_SERVER_REGISTERS in config:
|
if CONF_SERVER_COURTESY_RESPONSE in config or CONF_SERVER_REGISTERS in config:
|
||||||
return modbus.final_validate_modbus_device("modbus_controller", role="server")(
|
return modbus.final_validate_modbus_device("modbus_controller", role="server")(
|
||||||
config
|
config
|
||||||
)
|
)
|
||||||
@@ -299,6 +313,20 @@ async def to_code(config):
|
|||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
cg.add(var.set_allow_duplicate_commands(config[CONF_ALLOW_DUPLICATE_COMMANDS]))
|
cg.add(var.set_allow_duplicate_commands(config[CONF_ALLOW_DUPLICATE_COMMANDS]))
|
||||||
cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE]))
|
cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE]))
|
||||||
|
if server_courtesy_response := config.get(CONF_SERVER_COURTESY_RESPONSE):
|
||||||
|
cg.add(
|
||||||
|
var.set_server_courtesy_response(
|
||||||
|
cg.StructInitializer(
|
||||||
|
ServerCourtesyResponse,
|
||||||
|
("enabled", server_courtesy_response[CONF_ENABLED]),
|
||||||
|
(
|
||||||
|
"register_last_address",
|
||||||
|
server_courtesy_response[CONF_REGISTER_LAST_ADDRESS],
|
||||||
|
),
|
||||||
|
("register_value", server_courtesy_response[CONF_REGISTER_VALUE]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
cg.add(var.set_max_cmd_retries(config[CONF_MAX_CMD_RETRIES]))
|
cg.add(var.set_max_cmd_retries(config[CONF_MAX_CMD_RETRIES]))
|
||||||
cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES]))
|
cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES]))
|
||||||
if CONF_SERVER_REGISTERS in config:
|
if CONF_SERVER_REGISTERS in config:
|
||||||
|
@@ -2,6 +2,7 @@ CONF_ALLOW_DUPLICATE_COMMANDS = "allow_duplicate_commands"
|
|||||||
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_ENABLED = "enabled"
|
||||||
CONF_OFFLINE_SKIP_UPDATES = "offline_skip_updates"
|
CONF_OFFLINE_SKIP_UPDATES = "offline_skip_updates"
|
||||||
CONF_CUSTOM_COMMAND = "custom_command"
|
CONF_CUSTOM_COMMAND = "custom_command"
|
||||||
CONF_FORCE_NEW_RANGE = "force_new_range"
|
CONF_FORCE_NEW_RANGE = "force_new_range"
|
||||||
@@ -13,8 +14,11 @@ CONF_ON_ONLINE = "on_online"
|
|||||||
CONF_ON_OFFLINE = "on_offline"
|
CONF_ON_OFFLINE = "on_offline"
|
||||||
CONF_RAW_ENCODE = "raw_encode"
|
CONF_RAW_ENCODE = "raw_encode"
|
||||||
CONF_REGISTER_COUNT = "register_count"
|
CONF_REGISTER_COUNT = "register_count"
|
||||||
|
CONF_REGISTER_LAST_ADDRESS = "register_last_address"
|
||||||
CONF_REGISTER_TYPE = "register_type"
|
CONF_REGISTER_TYPE = "register_type"
|
||||||
|
CONF_REGISTER_VALUE = "register_value"
|
||||||
CONF_RESPONSE_SIZE = "response_size"
|
CONF_RESPONSE_SIZE = "response_size"
|
||||||
|
CONF_SERVER_COURTESY_RESPONSE = "server_courtesy_response"
|
||||||
CONF_SKIP_UPDATES = "skip_updates"
|
CONF_SKIP_UPDATES = "skip_updates"
|
||||||
CONF_USE_WRITE_MULTIPLE = "use_write_multiple"
|
CONF_USE_WRITE_MULTIPLE = "use_write_multiple"
|
||||||
CONF_VALUE_TYPE = "value_type"
|
CONF_VALUE_TYPE = "value_type"
|
||||||
|
@@ -112,6 +112,12 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t
|
|||||||
"0x%X.",
|
"0x%X.",
|
||||||
this->address_, function_code, start_address, number_of_registers);
|
this->address_, function_code, start_address, number_of_registers);
|
||||||
|
|
||||||
|
if (number_of_registers == 0 || number_of_registers > modbus::MAX_NUM_OF_REGISTERS_TO_READ) {
|
||||||
|
ESP_LOGW(TAG, "Invalid number of registers %d. Sending exception response.", number_of_registers);
|
||||||
|
this->send_error(function_code, ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<uint16_t> sixteen_bit_response;
|
std::vector<uint16_t> sixteen_bit_response;
|
||||||
for (uint16_t current_address = start_address; current_address < start_address + number_of_registers;) {
|
for (uint16_t current_address = start_address; current_address < start_address + number_of_registers;) {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
@@ -136,9 +142,21 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
ESP_LOGW(TAG, "Could not match any register to address %02X. Sending exception response.", current_address);
|
if (this->server_courtesy_response_.enabled &&
|
||||||
send_error(function_code, 0x02);
|
(current_address <= this->server_courtesy_response_.register_last_address)) {
|
||||||
return;
|
ESP_LOGD(TAG,
|
||||||
|
"Could not match any register to address 0x%02X, but default allowed. "
|
||||||
|
"Returning default value: %d.",
|
||||||
|
current_address, this->server_courtesy_response_.register_value);
|
||||||
|
sixteen_bit_response.push_back(this->server_courtesy_response_.register_value);
|
||||||
|
current_address += 1; // Just increment by 1, as the default response is a single register
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG,
|
||||||
|
"Could not match any register to address 0x%02X and default not allowed. Sending exception response.",
|
||||||
|
current_address);
|
||||||
|
this->send_error(function_code, ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,27 +174,27 @@ void ModbusController::on_modbus_write_registers(uint8_t function_code, const st
|
|||||||
uint16_t number_of_registers;
|
uint16_t number_of_registers;
|
||||||
uint16_t payload_offset;
|
uint16_t payload_offset;
|
||||||
|
|
||||||
if (function_code == 0x10) {
|
if (function_code == ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS) {
|
||||||
number_of_registers = uint16_t(data[3]) | (uint16_t(data[2]) << 8);
|
number_of_registers = uint16_t(data[3]) | (uint16_t(data[2]) << 8);
|
||||||
if (number_of_registers == 0 || number_of_registers > 0x7B) {
|
if (number_of_registers == 0 || number_of_registers > modbus::MAX_NUM_OF_REGISTERS_TO_WRITE) {
|
||||||
ESP_LOGW(TAG, "Invalid number of registers %d. Sending exception response.", number_of_registers);
|
ESP_LOGW(TAG, "Invalid number of registers %d. Sending exception response.", number_of_registers);
|
||||||
send_error(function_code, 3);
|
this->send_error(function_code, ModbusExceptionCode::ILLEGAL_DATA_VALUE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uint16_t payload_size = data[4];
|
uint16_t payload_size = data[4];
|
||||||
if (payload_size != number_of_registers * 2) {
|
if (payload_size != number_of_registers * 2) {
|
||||||
ESP_LOGW(TAG, "Payload size of %d bytes is not 2 times the number of registers (%d). Sending exception response.",
|
ESP_LOGW(TAG, "Payload size of %d bytes is not 2 times the number of registers (%d). Sending exception response.",
|
||||||
payload_size, number_of_registers);
|
payload_size, number_of_registers);
|
||||||
send_error(function_code, 3);
|
this->send_error(function_code, ModbusExceptionCode::ILLEGAL_DATA_VALUE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
payload_offset = 5;
|
payload_offset = 5;
|
||||||
} else if (function_code == 0x06) {
|
} else if (function_code == ModbusFunctionCode::WRITE_SINGLE_REGISTER) {
|
||||||
number_of_registers = 1;
|
number_of_registers = 1;
|
||||||
payload_offset = 2;
|
payload_offset = 2;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "Invalid function code 0x%X. Sending exception response.", function_code);
|
ESP_LOGW(TAG, "Invalid function code 0x%X. Sending exception response.", function_code);
|
||||||
send_error(function_code, 1);
|
this->send_error(function_code, ModbusExceptionCode::ILLEGAL_FUNCTION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +229,7 @@ void ModbusController::on_modbus_write_registers(uint8_t function_code, const st
|
|||||||
if (!for_each_register([](ServerRegister *server_register, uint16_t offset) -> bool {
|
if (!for_each_register([](ServerRegister *server_register, uint16_t offset) -> bool {
|
||||||
return server_register->write_lambda != nullptr;
|
return server_register->write_lambda != nullptr;
|
||||||
})) {
|
})) {
|
||||||
send_error(function_code, 1);
|
this->send_error(function_code, ModbusExceptionCode::ILLEGAL_FUNCTION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +238,7 @@ void ModbusController::on_modbus_write_registers(uint8_t function_code, const st
|
|||||||
int64_t number = payload_to_number(data, server_register->value_type, offset, 0xFFFFFFFF);
|
int64_t number = payload_to_number(data, server_register->value_type, offset, 0xFFFFFFFF);
|
||||||
return server_register->write_lambda(number);
|
return server_register->write_lambda(number);
|
||||||
})) {
|
})) {
|
||||||
send_error(function_code, 4);
|
this->send_error(function_code, ModbusExceptionCode::SERVICE_DEVICE_FAILURE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,8 +449,15 @@ void ModbusController::dump_config() {
|
|||||||
"ModbusController:\n"
|
"ModbusController:\n"
|
||||||
" Address: 0x%02X\n"
|
" Address: 0x%02X\n"
|
||||||
" Max Command Retries: %d\n"
|
" Max Command Retries: %d\n"
|
||||||
" Offline Skip Updates: %d",
|
" Offline Skip Updates: %d\n"
|
||||||
this->address_, this->max_cmd_retries_, this->offline_skip_updates_);
|
" Server Courtesy Response:\n"
|
||||||
|
" Enabled: %s\n"
|
||||||
|
" Register Last Address: 0x%02X\n"
|
||||||
|
" Register Value: %d",
|
||||||
|
this->address_, this->max_cmd_retries_, this->offline_skip_updates_,
|
||||||
|
this->server_courtesy_response_.enabled ? "true" : "false",
|
||||||
|
this->server_courtesy_response_.register_last_address, this->server_courtesy_response_.register_value);
|
||||||
|
|
||||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||||
ESP_LOGCONFIG(TAG, "sensormap");
|
ESP_LOGCONFIG(TAG, "sensormap");
|
||||||
for (auto &it : this->sensorset_) {
|
for (auto &it : this->sensorset_) {
|
||||||
|
@@ -16,35 +16,9 @@ namespace modbus_controller {
|
|||||||
|
|
||||||
class ModbusController;
|
class ModbusController;
|
||||||
|
|
||||||
enum class ModbusFunctionCode {
|
using modbus::ModbusFunctionCode;
|
||||||
CUSTOM = 0x00,
|
using modbus::ModbusRegisterType;
|
||||||
READ_COILS = 0x01,
|
using modbus::ModbusExceptionCode;
|
||||||
READ_DISCRETE_INPUTS = 0x02,
|
|
||||||
READ_HOLDING_REGISTERS = 0x03,
|
|
||||||
READ_INPUT_REGISTERS = 0x04,
|
|
||||||
WRITE_SINGLE_COIL = 0x05,
|
|
||||||
WRITE_SINGLE_REGISTER = 0x06,
|
|
||||||
READ_EXCEPTION_STATUS = 0x07, // not implemented
|
|
||||||
DIAGNOSTICS = 0x08, // not implemented
|
|
||||||
GET_COMM_EVENT_COUNTER = 0x0B, // not implemented
|
|
||||||
GET_COMM_EVENT_LOG = 0x0C, // not implemented
|
|
||||||
WRITE_MULTIPLE_COILS = 0x0F,
|
|
||||||
WRITE_MULTIPLE_REGISTERS = 0x10,
|
|
||||||
REPORT_SERVER_ID = 0x11, // not implemented
|
|
||||||
READ_FILE_RECORD = 0x14, // not implemented
|
|
||||||
WRITE_FILE_RECORD = 0x15, // not implemented
|
|
||||||
MASK_WRITE_REGISTER = 0x16, // not implemented
|
|
||||||
READ_WRITE_MULTIPLE_REGISTERS = 0x17, // not implemented
|
|
||||||
READ_FIFO_QUEUE = 0x18, // not implemented
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class ModbusRegisterType : uint8_t {
|
|
||||||
CUSTOM = 0x0,
|
|
||||||
COIL = 0x01,
|
|
||||||
DISCRETE_INPUT = 0x02,
|
|
||||||
HOLDING = 0x03,
|
|
||||||
READ = 0x04,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class SensorValueType : uint8_t {
|
enum class SensorValueType : uint8_t {
|
||||||
RAW = 0x00, // variable length
|
RAW = 0x00, // variable length
|
||||||
@@ -256,6 +230,12 @@ class SensorItem {
|
|||||||
bool force_new_range{false};
|
bool force_new_range{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ServerCourtesyResponse {
|
||||||
|
bool enabled{false};
|
||||||
|
uint16_t register_last_address{0xFFFF};
|
||||||
|
uint16_t register_value{0};
|
||||||
|
};
|
||||||
|
|
||||||
class ServerRegister {
|
class ServerRegister {
|
||||||
using ReadLambda = std::function<int64_t()>;
|
using ReadLambda = std::function<int64_t()>;
|
||||||
using WriteLambda = std::function<bool(int64_t value)>;
|
using WriteLambda = std::function<bool(int64_t value)>;
|
||||||
@@ -530,6 +510,12 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
|
|||||||
void set_max_cmd_retries(uint8_t max_cmd_retries) { this->max_cmd_retries_ = max_cmd_retries; }
|
void set_max_cmd_retries(uint8_t max_cmd_retries) { this->max_cmd_retries_ = max_cmd_retries; }
|
||||||
/// get how many times a command will be (re)sent if no response is received
|
/// get how many times a command will be (re)sent if no response is received
|
||||||
uint8_t get_max_cmd_retries() { return this->max_cmd_retries_; }
|
uint8_t get_max_cmd_retries() { return this->max_cmd_retries_; }
|
||||||
|
/// Called by esphome generated code to set the server courtesy response object
|
||||||
|
void set_server_courtesy_response(const ServerCourtesyResponse &server_courtesy_response) {
|
||||||
|
this->server_courtesy_response_ = server_courtesy_response;
|
||||||
|
}
|
||||||
|
/// Get the server courtesy response object
|
||||||
|
ServerCourtesyResponse get_server_courtesy_response() const { return this->server_courtesy_response_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// parse sensormap_ and create range of sequential addresses
|
/// parse sensormap_ and create range of sequential addresses
|
||||||
@@ -572,6 +558,9 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
|
|||||||
CallbackManager<void(int, int)> online_callback_{};
|
CallbackManager<void(int, int)> online_callback_{};
|
||||||
/// Server offline callback
|
/// Server offline callback
|
||||||
CallbackManager<void(int, int)> offline_callback_{};
|
CallbackManager<void(int, int)> offline_callback_{};
|
||||||
|
/// Server courtesy response
|
||||||
|
ServerCourtesyResponse server_courtesy_response_{
|
||||||
|
.enabled = false, .register_last_address = 0xFFFF, .register_value = 0};
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Convert vector<uint8_t> response payload to float.
|
/** Convert vector<uint8_t> response payload to float.
|
||||||
|
@@ -45,6 +45,22 @@ modbus_controller:
|
|||||||
printf("address=%d, value=%d", x);
|
printf("address=%d, value=%d", x);
|
||||||
return true;
|
return true;
|
||||||
max_cmd_retries: 0
|
max_cmd_retries: 0
|
||||||
|
- id: modbus_controller4
|
||||||
|
modbus_id: mod_bus2
|
||||||
|
address: 0x4
|
||||||
|
server_courtesy_response:
|
||||||
|
enabled: true
|
||||||
|
register_last_address: 100
|
||||||
|
register_value: 0
|
||||||
|
server_registers:
|
||||||
|
- address: 0x0001
|
||||||
|
value_type: U_WORD
|
||||||
|
read_lambda: |-
|
||||||
|
return 0x8;
|
||||||
|
- address: 0x0005
|
||||||
|
value_type: U_WORD
|
||||||
|
read_lambda: |-
|
||||||
|
return (random_uint32() % 100);
|
||||||
binary_sensor:
|
binary_sensor:
|
||||||
- platform: modbus_controller
|
- platform: modbus_controller
|
||||||
modbus_controller_id: modbus_controller1
|
modbus_controller_id: modbus_controller1
|
||||||
|
Reference in New Issue
Block a user