[modbus] Restore function code byte for custom codes in deprecated on_modbus_data (#18006)
CI / Create common environment (push) Canceled after 0s
CI / Determine which jobs to run (push) Canceled after 0s
CI / Run script/ci-custom (push) Canceled after 0s
CI / Check pylint (push) Canceled after 0s
CI / pre-commit.ci lite (push) Canceled after 0s
CI / Seed pre-commit cache (push) Canceled after 0s
CI / Run pytest (macOS-latest, 3.12) (push) Canceled after 0s
CI / Run pytest (macOS-latest, 3.14) (push) Canceled after 0s
CI / Run pytest (ubuntu-latest, 3.12) (push) Canceled after 0s
CI / Run pytest (ubuntu-latest, 3.13) (push) Canceled after 0s
CI / Run pytest (ubuntu-latest, 3.14) (push) Canceled after 0s
CI / Run pytest (windows-latest, 3.12) (push) Canceled after 0s
CI / Run pytest (windows-latest, 3.14) (push) Canceled after 0s
CI / Report no coverage to Codecov (push) Canceled after 0s
CI / Run integration tests () (push) Canceled after 0s
CI / Check import esphome.__main__ time (push) Canceled after 0s
CI / Run CodSpeed benchmarks (push) Canceled after 0s
CI / Run C++ unit tests (push) Canceled after 0s
CI / Run script/clang-tidy for LibreTiny (push) Canceled after 0s
CI / Run script/clang-tidy for ESP8266 (push) Canceled after 0s
CI / Run script/clang-tidy for RP2 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 Arduino (push) Canceled after 0s
CI / Run script/clang-tidy for ZEPHYR (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 IDF (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 IDF 1/3 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 IDF 2/3 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 IDF 3/3 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 C6 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 P4 (push) Canceled after 0s
CI / Run script/clang-tidy for ESP32 S3 (push) Canceled after 0s
CI / Test components batch () (push) Canceled after 0s
CI / Test esp32 components with PlatformIO (push) Canceled after 0s
CI / Test downstream esphome/device-builder (push) Canceled after 0s
CI / Build target branch for memory impact (push) Canceled after 0s
CI / Build PR branch for memory impact (push) Canceled after 0s
CI / Comment memory impact (push) Canceled after 0s
CI / CI Status (push) Canceled after 0s

This commit is contained in:
Bonne Eggleston
2026-08-02 11:40:34 -05:00
committed by GitHub
parent e36d7fe82b
commit fcc2c26367
2 changed files with 47 additions and 1 deletions
+6 -1
View File
@@ -550,7 +550,12 @@ class ESPDEPRECATED("Subclass ModbusClientDevice and override on_response()/on_e
virtual void on_modbus_error(uint8_t function_code, uint8_t exception_code) {}
void on_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu) override {
auto payload = helpers::server_pdu_payload(response_pdu);
// Custom (user-defined) function codes historically delivered the payload starting AT the function
// code byte (frame data_offset 1). server_pdu_payload() drops that byte, so pass the whole PDU for
// them - external components match the first byte against the code they sent (issue #17994).
auto payload = !response_pdu.empty() && helpers::is_function_code_custom(response_pdu[0])
? response_pdu
: helpers::server_pdu_payload(response_pdu);
this->on_modbus_data(std::vector<uint8_t>(payload.begin(), payload.end()));
}
void on_error(std::span<const uint8_t> request_pdu, ExceptionCode exception_code) override {
@@ -341,4 +341,45 @@ TEST(ModbusTypedDispatch, SingleWriteAckPrefersTheResponseEcho) {
EXPECT_EQ(device.write_single_register_calls.back().value, 0x002A); // exception: request copy
}
// Deprecated on_modbus_data() compatibility shim (pre-2026.8 API). Records the vectors delivered to
// the old callback so we can pin its payload framing against the pre-2026.7 behavior.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
namespace {
class LegacyDevice : public ModbusDevice {
public:
void on_modbus_data(const std::vector<uint8_t> &data) override { this->data_calls.push_back(data); }
void on_modbus_error(uint8_t function_code, uint8_t exception_code) override {
this->error_calls.emplace_back(function_code, exception_code);
}
std::vector<std::vector<uint8_t>> data_calls;
std::vector<std::pair<uint8_t, uint8_t>> error_calls;
};
} // namespace
// Custom (user-defined) function codes historically delivered the payload INCLUDING the function code
// byte (frame data_offset 1). External components such as the Century VS pump match that first byte
// against the code they sent, so dropping it (issue #17994) makes every response get ignored.
TEST(ModbusLegacyShim, CustomFunctionCodeKeepsFunctionCodeByte) {
LegacyDevice device;
const uint8_t request[] = {0x45, 0x01, 0x02}; // custom function 0x45
const uint8_t response[] = {0x45, 0xAA, 0xBB, 0xCC}; // echo of the custom code + data
device.on_response(request, response);
ASSERT_EQ(device.data_calls.size(), 1u);
EXPECT_EQ(device.data_calls.front(), (std::vector<uint8_t>{0x45, 0xAA, 0xBB, 0xCC}));
}
// Standard reads still strip the function code and byte-count header, matching the pre-2026.7 shim.
TEST(ModbusLegacyShim, StandardReadStripsHeader) {
LegacyDevice device;
const uint8_t request[] = {0x03, 0x01, 0x00, 0x00, 0x02};
const uint8_t response[] = {0x03, 0x04, 0x00, 0x2A, 0x01, 0x00};
device.on_response(request, response);
ASSERT_EQ(device.data_calls.size(), 1u);
EXPECT_EQ(device.data_calls.front(), (std::vector<uint8_t>{0x00, 0x2A, 0x01, 0x00}));
}
#pragma GCC diagnostic pop
} // namespace esphome::modbus::testing