[modbus] Turn the ModbusDevice alias into a working compatibility shim (#17854)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bonne Eggleston
2026-07-25 09:39:07 -10:00
committed by GitHub
co-authored by Claude Fable 5
parent eaa79b3696
commit 0833e91fb5
2 changed files with 73 additions and 4 deletions
+21 -4
View File
@@ -252,10 +252,27 @@ class ModbusClientDevice {
uint8_t address_{0}; uint8_t address_{0};
}; };
// This is for compatibility with external components using the former class name // Compatibility shim for external components written against the pre-2026.8 API, which subclassed
// Remove before 2026.12.0 // ModbusDevice and overrode on_modbus_data()/on_modbus_error(). The name is free (nothing in-tree
using ModbusDevice ESPDEPRECATED("Use ModbusClientDevice instead. Removed in 2026.12.0", // uses it), so instead of a plain alias it adapts the new span-based hooks back to the old
"2026.6.0") = ModbusClientDevice; // signatures: on_modbus_data() receives the response payload as an owning vector (the heap copy
// exists only on this deprecated path) and on_modbus_error() the function code and exception code.
// Remove before 2027.2.0 (window restarted when the plain alias became a behavior shim in 2026.8.0)
class ESPDEPRECATED("Subclass ModbusClientDevice and override on_response()/on_error() instead. Removed in 2027.2.0",
"2026.8.0") ModbusDevice : public ModbusClientDevice {
public:
using ModbusClientDevice::ModbusClientDevice;
virtual void on_modbus_data(const std::vector<uint8_t> &data) {}
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);
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 {
this->on_modbus_error(request_pdu.empty() ? 0 : request_pdu[0], static_cast<uint8_t>(exception_code));
}
};
// Register values exchanged with server handlers, in host byte order. Sized at the larger of the two protocol // 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 // maxima (read = 125 / 0x7D, write = 123 / 0x7B); the per-direction count limit is enforced by the hub, not by
@@ -220,4 +220,56 @@ TEST(ModbusClientHub, OversizedPduIsRefusedWithNotSent) {
EXPECT_TRUE(hub.tx_buffer_empty()); EXPECT_TRUE(hub.tx_buffer_empty());
} }
// --- ModbusDevice compatibility shim ------------------------------------------------------------
// External components written against the pre-2026.8 API subclass ModbusDevice and override the
// old callbacks; the shim adapts the span-based hooks back to those signatures.
namespace {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
class LegacyApiDevice : public ModbusDevice {
public:
LegacyApiDevice(ModbusClientHub *hub, uint8_t address) : ModbusDevice(hub, address) {}
void on_modbus_data(const std::vector<uint8_t> &data) override { this->last_data_ = data; }
void on_modbus_error(uint8_t function_code, uint8_t exception_code) override {
this->last_error_fc_ = function_code;
this->last_error_code_ = exception_code;
}
std::vector<uint8_t> last_data_;
int last_error_fc_{-1};
int last_error_code_{-1};
};
#pragma GCC diagnostic pop
} // namespace
TEST(ModbusDeviceShim, LegacyCallbacksReceiveTheOldShapes) {
NoResponseProbeHub hub;
LegacyApiDevice device(&hub, 0x02);
// Read response: on_modbus_data() historically received the payload after the function code and
// the byte-count byte, as an owning vector.
const uint8_t read_req[] = {0x03, 0x00, 0x10, 0x00, 0x02};
device.send_pdu(read_req);
hub.force_send_front();
const uint8_t response[] = {0x03, 0x04, 0x00, 0x2A, 0x01, 0x00};
hub.receive_frame_for_test(0x02, response);
const std::vector<uint8_t> expected{0x00, 0x2A, 0x01, 0x00};
EXPECT_EQ(device.last_data_, expected);
// Write echo: no byte-count byte, so the payload is everything after the function code.
const uint8_t write_req[] = {0x06, 0x00, 0x10, 0x00, 0x2A};
device.send_pdu(write_req);
hub.force_send_front();
hub.receive_frame_for_test(0x02, write_req); // single-write responses echo the request
const std::vector<uint8_t> expected_echo{0x00, 0x10, 0x00, 0x2A};
EXPECT_EQ(device.last_data_, expected_echo);
// Exception response: on_modbus_error() received the masked function code and the exception code.
device.send_pdu(read_req);
hub.force_send_front();
const uint8_t error[] = {0x83, 0x02};
hub.receive_frame_for_test(0x02, error);
EXPECT_EQ(device.last_error_fc_, 0x03);
EXPECT_EQ(device.last_error_code_, 0x02);
}
} // namespace esphome::modbus::testing } // namespace esphome::modbus::testing