modbus_rtu_lib: define read holding register
Add the modbus rtu library commands with initial support for read
holding register. Define a base Message classes which all subsequent and
specific request & response messages can inherit from. Also add the
relevant unit testing for the added command set.
Tested:
```
> meson test -C builddir
ninja: Entering directory `/host/repos/Modbus/phosphor-modbus/builddir'
ninja: no work to do.
1/1 test_modbus_commands OK 0.01s
Ok: 1
Fail: 0
```
Change-Id: I331b0dee66a0829e9352ae0eac8ac82a9150904c
Signed-off-by: Jagpal Singh Gill <paligill@gmail.com>
diff --git a/tests/meson.build b/tests/meson.build
index 8b13789..a02a690 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -1 +1,30 @@
+gtest_dep = dependency('gtest', main: true, disabler: true, required: false)
+gmock_dep = dependency('gmock', disabler: true, required: false)
+if not gtest_dep.found() or not gmock_dep.found()
+ gtest_proj = import('cmake').subproject('googletest', required: false)
+ if gtest_proj.found()
+ gtest_dep = declare_dependency(
+ dependencies: [
+ dependency('threads'),
+ gtest_proj.dependency('gtest'),
+ gtest_proj.dependency('gtest_main'),
+ ],
+ )
+ gmock_dep = gtest_proj.dependency('gmock')
+ else
+ assert(
+ not get_option('tests').enabled(),
+ 'Googletest is required if tests are enabled',
+ )
+ endif
+endif
+test(
+ 'test_modbus_commands',
+ executable(
+ 'test_modbus_commands',
+ 'test_modbus_commands.cpp',
+ dependencies: [gtest_dep, gmock_dep, default_deps, modbus_rtu_dep],
+ include_directories: ['.'],
+ ),
+)
diff --git a/tests/test_modbus_commands.cpp b/tests/test_modbus_commands.cpp
new file mode 100644
index 0000000..547e0b2
--- /dev/null
+++ b/tests/test_modbus_commands.cpp
@@ -0,0 +1,177 @@
+#include "modbus/modbus_commands.hpp"
+#include "modbus/modbus_exception.hpp"
+
+#include <cstring>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace RTUIntf = phosphor::modbus::rtu;
+
+class ReadHoldingRegistersResponseTest :
+ public RTUIntf::ReadHoldingRegistersResponse
+{
+ public:
+ ReadHoldingRegistersResponseTest(uint8_t address,
+ std::vector<uint16_t>& registers) :
+ RTUIntf::ReadHoldingRegistersResponse(address, registers)
+ {}
+
+ template <std::size_t Length>
+ ReadHoldingRegistersResponseTest& operator=(
+ const std::array<uint8_t, Length>& expectedMessage)
+ {
+ len = Length;
+ std::copy(expectedMessage.begin(), expectedMessage.end(), raw.begin());
+ return *this;
+ }
+};
+
+class ModbusCommandTest : public ::testing::Test
+{
+ public:
+ ModbusCommandTest() = default;
+
+ static constexpr auto failureMessageLength = 11;
+
+ template <typename ExceptionType, std::size_t Length>
+ void TestReadHoldingRegisterResponseFailure(
+ const std::array<uint8_t, Length>& expectedMessage)
+ {
+ constexpr uint8_t expectedAddress = 0xa;
+ constexpr size_t expectedRegisterSize = 3;
+ std::vector<uint16_t> registers(expectedRegisterSize);
+ ReadHoldingRegistersResponseTest response(expectedAddress, registers);
+ response = expectedMessage;
+
+ EXPECT_THROW(response.decode(), ExceptionType);
+ }
+};
+
+TEST_F(ModbusCommandTest, TestReadHoldingRegistersRequestSucess)
+{
+ constexpr size_t expectedLength = 8;
+ constexpr uint8_t expectedAddress = 0x1;
+ constexpr std::array<uint8_t, expectedLength> expectedMessage = {
+ expectedAddress, // addr(1) = 0x01
+ 0x03, // func(1) = 0x03
+ 0x12, // reg_off(2) = 0x1234
+ 0x34,
+ 0x00, // reg_cnt(2) = 0x0020,
+ 0x20,
+ 0x00, // crc(2) (Pre-computed) = 0xa4
+ 0xa4};
+
+ RTUIntf::ReadHoldingRegistersRequest request(expectedAddress, 0x1234, 0x20);
+ request.encode();
+
+ std::array<uint8_t, expectedLength> actualMessage;
+ std::memcpy(actualMessage.data(), request.raw.data(), expectedLength);
+
+ EXPECT_EQ(actualMessage, expectedMessage);
+ EXPECT_EQ(request.address, expectedAddress);
+}
+
+TEST_F(ModbusCommandTest, TestReadHoldingRegistersResponseSucess)
+{
+ constexpr size_t expectedLength = 11;
+ constexpr uint8_t expectedAddress = 0xa;
+ constexpr size_t expectedRegisterSize = 3;
+ constexpr std::array<uint8_t, expectedLength> expectedMessage = {
+ expectedAddress, // addr(1) = 0x0a
+ 0x03, // func(1) = 0x03
+ 0x06, // bytes(1) = 0x06
+ 0x11, // regs(3*2) = 0x1122, 0x3344, 0x5566
+ 0x22,
+ 0x33,
+ 0x44,
+ 0x55,
+ 0x66,
+ 0x59, // crc(2) = 0x5928 (pre-computed)
+ 0x28};
+ const std::vector<uint16_t> expectedRegisters{0x1122, 0x3344, 0x5566};
+ std::vector<uint16_t> registers(expectedRegisterSize);
+
+ RTUIntf::ReadHoldingRegistersResponse response(expectedAddress, registers);
+ EXPECT_EQ(response.len, expectedLength);
+
+ std::copy(expectedMessage.begin(), expectedMessage.end(),
+ response.raw.begin());
+
+ EXPECT_EQ(response.len, expectedLength);
+ response.decode();
+ EXPECT_EQ(registers, expectedRegisters);
+}
+
+TEST_F(ModbusCommandTest, TestReadHoldingRegistersResponseError)
+{
+ constexpr std::array<uint8_t, 5> expectedMessage = {
+ 0xa, // addr(1) = 0x0a
+ 0x83, // func(1) = 0x83
+ 0x03, // exception code(1) = 0x03
+ 0x70, // crc(2) = 0x70f3 (pre-computed)
+ 0xf3};
+
+ TestReadHoldingRegisterResponseFailure<RTUIntf::ModbusException>(
+ expectedMessage);
+}
+
+TEST_F(ModbusCommandTest, TestReadHoldingRegistersResponseBadAddress)
+{
+ constexpr std::array<uint8_t, failureMessageLength> expectedMessage = {
+ 0x1, // Bad address(1), should be 0x0a
+ 0x03, // func(1) = 0x03
+ 0x06, // bytes(1) = 0x06
+ 0x11, // regs(3*2) = 0x1122, 0x3344, 0x5566
+ 0x22, 0x33, 0x44, 0x55, 0x66,
+ 0x2a, // crc(2) = 0x2a18 (pre-computed)
+ 0x18};
+
+ TestReadHoldingRegisterResponseFailure<RTUIntf::ModbusBadResponseException>(
+ expectedMessage);
+}
+
+TEST_F(ModbusCommandTest, TestReadHoldingRegistersResponseBadCRC)
+{
+ constexpr std::array<uint8_t, failureMessageLength> expectedMessage = {
+ 0xa, // addr(1) = 0x0a
+ 0x03, // func(1) = 0x03
+ 0x06, // bytes(1) = 0x06
+ 0x11, // regs(3*2) = 0x1122, 0x3344, 0x5566
+ 0x22, 0x33, 0x44, 0x55, 0x66,
+ 0x59, // Bad crc(2), should be 0x5928
+ 0x29};
+
+ TestReadHoldingRegisterResponseFailure<RTUIntf::ModbusCRCException>(
+ expectedMessage);
+}
+
+TEST_F(ModbusCommandTest, TestReadHoldingRegistersResponseBadFunctionCode)
+{
+ constexpr std::array<uint8_t, failureMessageLength> expectedMessage = {
+ 0xa, // addr(1) = 0x0a
+ 0x04, // Bad function code(1), should be 0x03
+ 0x06, // bytes(1) = 0x06
+ 0x11, // regs(3*2) = 0x1122, 0x3344, 0x5566
+ 0x22, 0x33, 0x44, 0x55, 0x66,
+ 0x18, // crc(2) = 0x18ce (pre-computed)
+ 0xce};
+
+ TestReadHoldingRegisterResponseFailure<RTUIntf::ModbusBadResponseException>(
+ expectedMessage);
+}
+
+TEST_F(ModbusCommandTest, TestReadHoldingRegistersResponseBadByteCount)
+{
+ constexpr std::array<uint8_t, failureMessageLength> expectedMessage = {
+ 0xa, // addr(1) = 0x0a
+ 0x03, // func(1) = 0x03
+ 0x04, // Bad bytes(1), should be 0x06
+ 0x11, // regs(3*2) = 0x1122, 0x3344, 0x5566
+ 0x22, 0x33, 0x44, 0x55, 0x66,
+ 0x7a, // crc(2) = 0x7ae8 (pre-computed)
+ 0xe8};
+
+ TestReadHoldingRegisterResponseFailure<RTUIntf::ModbusBadResponseException>(
+ expectedMessage);
+}