diff --git a/esphome/components/modbus/helpers.py b/esphome/components/modbus/helpers.py index 6f97f1e605..9d7dc71547 100644 --- a/esphome/components/modbus/helpers.py +++ b/esphome/components/modbus/helpers.py @@ -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") diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 488bcf1459..eefab7967f 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -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(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(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; diff --git a/esphome/components/modbus/modbus.h b/esphome/components/modbus/modbus.h index b0f2aed9f8..d995c441ad 100644 --- a/esphome/components/modbus/modbus.h +++ b/esphome/components/modbus/modbus.h @@ -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; +// 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; // 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; }; diff --git a/esphome/components/modbus/modbus_definitions.h b/esphome/components/modbus/modbus_definitions.h index 1c03498f1d..a5bcc1e3fc 100644 --- a/esphome/components/modbus/modbus_definitions.h +++ b/esphome/components/modbus/modbus_definitions.h @@ -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: diff --git a/esphome/components/modbus/modbus_helpers.h b/esphome/components/modbus/modbus_helpers.h index b7b9020945..fef0f915ea 100644 --- a/esphome/components/modbus/modbus_helpers.h +++ b/esphome/components/modbus/modbus_helpers.h @@ -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; diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index cdbba54c1f..527e9b047f 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -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, diff --git a/esphome/components/modbus_server/modbus_server.cpp b/esphome/components/modbus_server/modbus_server.cpp index 44b1b160a5..1f787a0b61 100644 --- a/esphome/components/modbus_server/modbus_server.cpp +++ b/esphome/components/modbus_server/modbus_server.cpp @@ -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()); diff --git a/esphome/components/modbus_server/modbus_server.h b/esphome/components/modbus_server/modbus_server.h index a5d193cb41..4fddd9854d 100644 --- a/esphome/components/modbus_server/modbus_server.h +++ b/esphome/components/modbus_server/modbus_server.h @@ -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; diff --git a/tests/components/modbus_server/modbus_server_test.cpp b/tests/components/modbus_server/modbus_server_test.cpp index 419bb9cf25..d95bb473c9 100644 --- a/tests/components/modbus_server/modbus_server_test.cpp +++ b/tests/components/modbus_server/modbus_server_test.cpp @@ -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); }