mirror of
https://github.com/esphome/esphome.git
synced 2026-09-24 05:24:14 +00:00
[modbus] API naming (#17378)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
2067da4ff5
commit
66ab807596
@@ -29,7 +29,7 @@ MODBUS_WRITE_REGISTER_TYPE = {
|
||||
MODBUS_REGISTER_TYPE = {
|
||||
**MODBUS_WRITE_REGISTER_TYPE,
|
||||
"discrete_input": ModbusRegisterType.DISCRETE_INPUT,
|
||||
"read": ModbusRegisterType.READ,
|
||||
"read": ModbusRegisterType.INPUT_REGISTER,
|
||||
}
|
||||
|
||||
SensorValueType_ns = modbus_helpers_ns.namespace("SensorValueType")
|
||||
|
||||
@@ -360,7 +360,7 @@ void ModbusServerHub::process_modbus_client_frame_(uint8_t address, uint8_t func
|
||||
return;
|
||||
}
|
||||
|
||||
ServerResponseStatus status;
|
||||
ResponseStatus status;
|
||||
uint8_t response_buffer[modbus::MAX_RAW_SIZE];
|
||||
const uint8_t *response_data = response_buffer;
|
||||
uint16_t response_len = 0;
|
||||
@@ -381,9 +381,9 @@ void ModbusServerHub::process_modbus_client_frame_(uint8_t address, uint8_t func
|
||||
}
|
||||
RegisterValues registers;
|
||||
if (static_cast<ModbusFunctionCode>(function_code) == ModbusFunctionCode::READ_HOLDING_REGISTERS) {
|
||||
status = device->on_modbus_read_holding_registers(start_address, number_of_registers, registers);
|
||||
status = device->on_read_holding_registers(start_address, number_of_registers, registers);
|
||||
} else {
|
||||
status = device->on_modbus_read_input_registers(start_address, number_of_registers, registers);
|
||||
status = device->on_read_input_registers(start_address, number_of_registers, registers);
|
||||
}
|
||||
|
||||
// A handler that returns an exception leaves registers partially filled, so check the exception
|
||||
@@ -436,7 +436,7 @@ void ModbusServerHub::process_modbus_client_frame_(uint8_t address, uint8_t func
|
||||
for (uint16_t i = 0; i < number_of_registers; i++) {
|
||||
registers.push_back(helpers::get_data<uint16_t>(data, values_offset + i * 2));
|
||||
}
|
||||
status = device->on_modbus_write_registers(start_address, registers);
|
||||
status = device->on_write_registers(start_address, registers);
|
||||
response_data = data; // echo the request header per Modbus 6.6, 6.12
|
||||
response_len = 4;
|
||||
break;
|
||||
|
||||
@@ -201,8 +201,9 @@ class ModbusClientDevice {
|
||||
using ModbusDevice ESPDEPRECATED("Use ModbusClientDevice instead. Removed in 2026.12.0",
|
||||
"2026.6.0") = ModbusClientDevice;
|
||||
|
||||
// Result of a server register handler: std::nullopt means success, otherwise the Modbus exception code to return.
|
||||
using ServerResponseStatus = std::optional<ModbusExceptionCode>;
|
||||
// Transaction status: std::nullopt on success, otherwise the Modbus exception code. Server handlers return it;
|
||||
// (future) client response callbacks receive it. Named without a side prefix so both directions share it.
|
||||
using ResponseStatus = std::optional<ModbusExceptionCode>;
|
||||
// Register values exchanged with server handlers, in host byte order. Sized at the larger of the two protocol
|
||||
// maxima (read = 125 / 0x7D, write = 123 / 0x7B); the per-direction count limit is enforced by the hub, not by
|
||||
// the capacity of this type.
|
||||
@@ -219,19 +220,19 @@ class ModbusServerDevice {
|
||||
ModbusServerDevice &operator=(ModbusServerDevice &&) = delete;
|
||||
void set_address(uint8_t address) { this->address_ = address; }
|
||||
uint8_t get_address() const { return this->address_; }
|
||||
virtual ServerResponseStatus on_modbus_read_registers(uint16_t start_address, uint16_t number_of_registers,
|
||||
RegisterValues ®isters) {
|
||||
virtual ResponseStatus on_read_registers(uint16_t start_address, uint16_t number_of_registers,
|
||||
RegisterValues ®isters) {
|
||||
return ModbusExceptionCode::ILLEGAL_FUNCTION;
|
||||
};
|
||||
virtual ServerResponseStatus on_modbus_read_input_registers(uint16_t start_address, uint16_t number_of_registers,
|
||||
RegisterValues ®isters) {
|
||||
return this->on_modbus_read_registers(start_address, number_of_registers, registers);
|
||||
virtual ResponseStatus on_read_input_registers(uint16_t start_address, uint16_t number_of_registers,
|
||||
RegisterValues ®isters) {
|
||||
return this->on_read_registers(start_address, number_of_registers, registers);
|
||||
};
|
||||
virtual ServerResponseStatus on_modbus_read_holding_registers(uint16_t start_address, uint16_t number_of_registers,
|
||||
RegisterValues ®isters) {
|
||||
return this->on_modbus_read_registers(start_address, number_of_registers, registers);
|
||||
virtual ResponseStatus on_read_holding_registers(uint16_t start_address, uint16_t number_of_registers,
|
||||
RegisterValues ®isters) {
|
||||
return this->on_read_registers(start_address, number_of_registers, registers);
|
||||
};
|
||||
virtual ServerResponseStatus on_modbus_write_registers(uint16_t start_address, const RegisterValues ®isters) {
|
||||
virtual ResponseStatus on_write_registers(uint16_t start_address, const RegisterValues ®isters) {
|
||||
return ModbusExceptionCode::ILLEGAL_FUNCTION;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::modbus {
|
||||
|
||||
@@ -48,7 +49,11 @@ enum class ModbusRegisterType : uint8_t {
|
||||
COIL = 0x01,
|
||||
DISCRETE_INPUT = 0x02,
|
||||
HOLDING = 0x03,
|
||||
READ = 0x04,
|
||||
// Named INPUT_REGISTER (not INPUT) because Arduino cores define INPUT as a macro.
|
||||
INPUT_REGISTER = 0x04,
|
||||
// Remove before 2027.2.0
|
||||
READ ESPDEPRECATED("Use ModbusRegisterType::INPUT_REGISTER instead. Removed in 2027.2.0", "2026.7.0") =
|
||||
INPUT_REGISTER,
|
||||
};
|
||||
|
||||
// 7 MODBUS Exception Responses:
|
||||
|
||||
@@ -90,7 +90,7 @@ inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_t
|
||||
return ModbusFunctionCode::READ_DISCRETE_INPUTS;
|
||||
case ModbusRegisterType::HOLDING:
|
||||
return ModbusFunctionCode::READ_HOLDING_REGISTERS;
|
||||
case ModbusRegisterType::READ:
|
||||
case ModbusRegisterType::INPUT_REGISTER:
|
||||
return ModbusFunctionCode::READ_INPUT_REGISTERS;
|
||||
default:
|
||||
return ModbusFunctionCode::INVALID;
|
||||
@@ -104,7 +104,7 @@ inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_
|
||||
case ModbusRegisterType::HOLDING:
|
||||
return multiple ? ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS : ModbusFunctionCode::WRITE_SINGLE_REGISTER;
|
||||
// These register types can't be written (per spec)
|
||||
case ModbusRegisterType::READ:
|
||||
case ModbusRegisterType::INPUT_REGISTER:
|
||||
case ModbusRegisterType::DISCRETE_INPUT:
|
||||
default:
|
||||
return ModbusFunctionCode::INVALID;
|
||||
|
||||
@@ -220,7 +220,7 @@ def function_code_to_register(function_code):
|
||||
"read_coils": ModbusRegisterType.COIL,
|
||||
"read_discrete_inputs": ModbusRegisterType.DISCRETE_INPUT,
|
||||
"read_holding_registers": ModbusRegisterType.HOLDING,
|
||||
"read_input_registers": ModbusRegisterType.READ,
|
||||
"read_input_registers": ModbusRegisterType.INPUT_REGISTER,
|
||||
"write_single_coil": ModbusRegisterType.COIL,
|
||||
"write_single_register": ModbusRegisterType.HOLDING,
|
||||
"write_multiple_coils": ModbusRegisterType.COIL,
|
||||
|
||||
@@ -27,9 +27,8 @@ ServerRegister *ModbusServer::find_containing_register_(uint32_t address) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
modbus::ServerResponseStatus ModbusServer::on_modbus_read_registers(uint16_t start_address,
|
||||
uint16_t number_of_registers,
|
||||
modbus::RegisterValues ®isters) {
|
||||
modbus::ResponseStatus ModbusServer::on_read_registers(uint16_t start_address, uint16_t number_of_registers,
|
||||
modbus::RegisterValues ®isters) {
|
||||
ESP_LOGV(TAG,
|
||||
"Received read holding/input registers for device 0x%X. Start address: 0x%X. Number of registers: 0x%X.",
|
||||
this->address_, start_address, number_of_registers);
|
||||
@@ -101,8 +100,8 @@ modbus::ServerResponseStatus ModbusServer::on_modbus_read_registers(uint16_t sta
|
||||
return {};
|
||||
}
|
||||
|
||||
modbus::ServerResponseStatus ModbusServer::on_modbus_write_registers(uint16_t start_address,
|
||||
const modbus::RegisterValues ®isters) {
|
||||
modbus::ResponseStatus ModbusServer::on_write_registers(uint16_t start_address,
|
||||
const modbus::RegisterValues ®isters) {
|
||||
// registers holds the values to write in host byte order; its size is the register count.
|
||||
ESP_LOGV(TAG, "Received write registers for device 0x%X. Start address: 0x%X. Number of registers: 0x%zX.",
|
||||
this->address_, start_address, registers.size());
|
||||
|
||||
@@ -102,11 +102,10 @@ class ModbusServer final : public Component, public modbus::ModbusServerDevice {
|
||||
/// Registers a server register with the controller. Called by esphomes code generator
|
||||
void add_server_register(ServerRegister *server_register) { server_registers_.push_back(server_register); }
|
||||
/// called when a modbus request (function code 0x03 or 0x04) was parsed without errors
|
||||
modbus::ServerResponseStatus on_modbus_read_registers(uint16_t start_address, uint16_t number_of_registers,
|
||||
modbus::RegisterValues ®isters) final;
|
||||
modbus::ResponseStatus on_read_registers(uint16_t start_address, uint16_t number_of_registers,
|
||||
modbus::RegisterValues ®isters) final;
|
||||
/// called when a modbus request (function code 0x06 or 0x10) was parsed without errors
|
||||
modbus::ServerResponseStatus on_modbus_write_registers(uint16_t start_address,
|
||||
const modbus::RegisterValues ®isters) final;
|
||||
modbus::ResponseStatus on_write_registers(uint16_t start_address, const modbus::RegisterValues ®isters) final;
|
||||
/// 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;
|
||||
|
||||
@@ -29,7 +29,7 @@ TEST(ModbusServerWrite, SingleWordSucceeds) {
|
||||
};
|
||||
server.add_server_register(®);
|
||||
|
||||
auto status = server.on_modbus_write_registers(0x0000, make_registers({0x1234}));
|
||||
auto status = server.on_write_registers(0x0000, make_registers({0x1234}));
|
||||
EXPECT_FALSE(status.has_value()); // nullopt == success
|
||||
EXPECT_EQ(written, 0x1234);
|
||||
}
|
||||
@@ -45,7 +45,7 @@ TEST(ModbusServerWrite, DwordSucceeds) {
|
||||
};
|
||||
server.add_server_register(®);
|
||||
|
||||
auto status = server.on_modbus_write_registers(0x0000, make_registers({0x1234, 0x5678}));
|
||||
auto status = server.on_write_registers(0x0000, make_registers({0x1234, 0x5678}));
|
||||
EXPECT_FALSE(status.has_value());
|
||||
EXPECT_EQ(written, 0x12345678);
|
||||
}
|
||||
@@ -70,7 +70,7 @@ TEST(ModbusServerWrite, UnderSuppliedValueAppliesNothing) {
|
||||
server.add_server_register(&dword_reg);
|
||||
|
||||
// Two words supplied: one for the WORD at 0x0000, but only one of the two the DWORD at 0x0001 needs.
|
||||
auto status = server.on_modbus_write_registers(0x0000, make_registers({0x1111, 0x2222}));
|
||||
auto status = server.on_write_registers(0x0000, make_registers({0x1111, 0x2222}));
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_VALUE);
|
||||
@@ -84,7 +84,7 @@ TEST(ModbusServerWrite, UnwritableRegisterRejected) {
|
||||
ServerRegister read_only(0x0000, SensorValueType::U_WORD, 1); // no write_lambda set
|
||||
server.add_server_register(&read_only);
|
||||
|
||||
auto status = server.on_modbus_write_registers(0x0000, make_registers({0x1234}));
|
||||
auto status = server.on_write_registers(0x0000, make_registers({0x1234}));
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
@@ -93,7 +93,7 @@ TEST(ModbusServerWrite, UnwritableRegisterRejected) {
|
||||
// An address with no registered register yields ILLEGAL_DATA_ADDRESS.
|
||||
TEST(ModbusServerWrite, UnmatchedAddressRejected) {
|
||||
ModbusServer server;
|
||||
auto status = server.on_modbus_write_registers(0x0005, make_registers({0x1234}));
|
||||
auto status = server.on_write_registers(0x0005, make_registers({0x1234}));
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
@@ -114,14 +114,14 @@ TEST(ModbusServerWrite, CallbackFailureIsServiceDeviceFailure) {
|
||||
server.add_server_register(&first);
|
||||
server.add_server_register(&second);
|
||||
|
||||
auto status = server.on_modbus_write_registers(0x0000, make_registers({0xAAAA, 0xBBBB}));
|
||||
auto status = server.on_write_registers(0x0000, make_registers({0xAAAA, 0xBBBB}));
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::SERVICE_DEVICE_FAILURE);
|
||||
EXPECT_TRUE(first_written); // pre-validation passed, so the first write applied before the failure
|
||||
}
|
||||
|
||||
// --- on_modbus_read_registers --------------------------------------------------
|
||||
// --- on_read_registers --------------------------------------------------
|
||||
|
||||
TEST(ModbusServerRead, SingleWordSucceeds) {
|
||||
ModbusServer server;
|
||||
@@ -130,7 +130,7 @@ TEST(ModbusServerRead, SingleWordSucceeds) {
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0000, 1, out);
|
||||
auto status = server.on_read_registers(0x0000, 1, out);
|
||||
EXPECT_FALSE(status.has_value());
|
||||
ASSERT_EQ(out.size(), 1u);
|
||||
EXPECT_EQ(out[0], 0x1234);
|
||||
@@ -143,7 +143,7 @@ TEST(ModbusServerRead, DwordReturnsTwoWordsHighFirst) {
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0000, 2, out);
|
||||
auto status = server.on_read_registers(0x0000, 2, out);
|
||||
EXPECT_FALSE(status.has_value());
|
||||
ASSERT_EQ(out.size(), 2u);
|
||||
EXPECT_EQ(out[0], 0x1234);
|
||||
@@ -165,7 +165,7 @@ TEST(ModbusServerRead, StartInsideValueRejected) {
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0011, 1, out); // the second cell of the DWORD
|
||||
auto status = server.on_read_registers(0x0011, 1, out); // the second cell of the DWORD
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
@@ -184,7 +184,7 @@ TEST(ModbusServerRead, ClippedTailRejected) {
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0000, 1, out); // only 1 of the DWORD's 2 registers
|
||||
auto status = server.on_read_registers(0x0000, 1, out); // only 1 of the DWORD's 2 registers
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
@@ -200,7 +200,7 @@ TEST(ModbusServerRead, WriteOnlyRegisterRejected) {
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0000, 1, out);
|
||||
auto status = server.on_read_registers(0x0000, 1, out);
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
@@ -213,7 +213,7 @@ TEST(ModbusServerRead, CourtesyDefaultForUnregistered) {
|
||||
ServerCourtesyResponse{.enabled = true, .register_last_address = 0xFFFF, .register_value = 0xABCD});
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0005, 2, out);
|
||||
auto status = server.on_read_registers(0x0005, 2, out);
|
||||
EXPECT_FALSE(status.has_value());
|
||||
ASSERT_EQ(out.size(), 2u);
|
||||
EXPECT_EQ(out[0], 0xABCD);
|
||||
@@ -224,7 +224,7 @@ TEST(ModbusServerRead, CourtesyDefaultForUnregistered) {
|
||||
TEST(ModbusServerRead, UnregisteredRejectedWithoutCourtesy) {
|
||||
ModbusServer server;
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0005, 1, out);
|
||||
auto status = server.on_read_registers(0x0005, 1, out);
|
||||
ASSERT_TRUE(status.has_value());
|
||||
if (status.has_value())
|
||||
EXPECT_EQ(status.value(), ModbusExceptionCode::ILLEGAL_DATA_ADDRESS);
|
||||
@@ -241,7 +241,7 @@ TEST(ModbusServerRead, PartialReadHighWord) {
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0010, 1, out);
|
||||
auto status = server.on_read_registers(0x0010, 1, out);
|
||||
EXPECT_FALSE(status.has_value());
|
||||
ASSERT_EQ(out.size(), 1u);
|
||||
EXPECT_EQ(out[0], 0x1234);
|
||||
@@ -256,7 +256,7 @@ TEST(ModbusServerRead, PartialReadLowWordFromInterior) {
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues out;
|
||||
auto status = server.on_modbus_read_registers(0x0011, 1, out);
|
||||
auto status = server.on_read_registers(0x0011, 1, out);
|
||||
EXPECT_FALSE(status.has_value());
|
||||
ASSERT_EQ(out.size(), 1u);
|
||||
EXPECT_EQ(out[0], 0x5678);
|
||||
@@ -272,12 +272,12 @@ TEST(ModbusServerRead, PartialReadReversedType) {
|
||||
server.add_server_register(®);
|
||||
|
||||
RegisterValues first;
|
||||
ASSERT_FALSE(server.on_modbus_read_registers(0x0010, 1, first).has_value());
|
||||
ASSERT_FALSE(server.on_read_registers(0x0010, 1, first).has_value());
|
||||
ASSERT_EQ(first.size(), 1u);
|
||||
EXPECT_EQ(first[0], 0x5678);
|
||||
|
||||
RegisterValues second;
|
||||
ASSERT_FALSE(server.on_modbus_read_registers(0x0011, 1, second).has_value());
|
||||
ASSERT_FALSE(server.on_read_registers(0x0011, 1, second).has_value());
|
||||
ASSERT_EQ(second.size(), 1u);
|
||||
EXPECT_EQ(second[0], 0x1234);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user