15#include <gtest/gtest.h>
33 auto a =
ParseMac(
"aa:bb:cc:dd:ee:ff");
34 auto b =
ParseMac(
"AA-BB-CC-DD-EE-FF");
35 auto c =
ParseMac(
"aa.bb.cc.dd.ee.ff");
37 ASSERT_TRUE(a.has_value());
38 ASSERT_TRUE(b.has_value());
39 ASSERT_TRUE(c.has_value());
43 EXPECT_EQ((*a)[0],
static_cast<std::uint8_t
>(0xAA));
44 EXPECT_EQ((*a)[5],
static_cast<std::uint8_t
>(0xFF));
50 EXPECT_FALSE(
ParseMac(
"my-device-name").has_value());
51 EXPECT_FALSE(
ParseMac(
"").has_value());
52 EXPECT_FALSE(
ParseMac(
"aa:bb:cc:dd:ee").has_value());
53 EXPECT_FALSE(
ParseMac(
"aa:bb:cc:dd:ee:ff:11").has_value());
54 EXPECT_FALSE(
ParseMac(
"gg:bb:cc:dd:ee:ff").has_value());
92 EXPECT_EQ(info.
OrderId(),
"MyOrder123");
104 info.
name =
"test-device";
105 info.ip =
"192.168.1.50";
111 EXPECT_EQ(dev.
Name(),
"test-device");
112 EXPECT_EQ(dev.
Ip(),
"192.168.1.50");
115 dev.
ToString().find(
"disconnected"),
134TEST(DeviceMoveTests, MoveConstructionDisconnectsSource)
137 0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
143 info.
name =
"test-device";
144 info.ip =
"192.168.1.50";
146 auto mock = std::make_unique<testing::NiceMock<rpc::MockRPCCon>>();
147 auto* rpc = mock.get();
148 ProfinetDevice source(info,
"eth0", kControllerMac, 5.0, std::move(mock));
154 EXPECT_EQ(destination.
Mac(),
"aa:bb:cc:dd:ee:ff");
155 EXPECT_EQ(destination.
Name(),
"test-device");
164TEST(DeviceMoveTests, MoveAssignmentReleasesDestinationFirst)
167 0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
173 info1.
name =
"test-device";
174 info1.ip =
"192.168.1.50";
176 auto mock1 = std::make_unique<testing::NiceMock<rpc::MockRPCCon>>();
178 ProfinetDevice source(info1,
"eth0", kControllerMac, 5.0, std::move(mock1));
184 info2.
name =
"test-device2";
185 info2.ip =
"192.168.1.80";
187 auto mockRpcDestination = std::make_unique<testing::NiceMock<rpc::MockRPCCon>>();
188 auto* rpcDestination = mockRpcDestination.get();
189 auto mockAlarmListenerDestination = std::make_unique<testing::NiceMock<MockAlarmListener>>();
191 auto* alarmListenerDestination = mockAlarmListenerDestination.get();
193 testing::InSequence sequence;
194 EXPECT_CALL(*alarmListenerDestination,
Stop)
196 EXPECT_CALL(*rpcDestination, Close)
199 ProfinetDevice destination(info2,
"eth0", kControllerMac, 5.0, std::move(mockRpcDestination), std::move(mockAlarmListenerDestination));
203 destination = std::move(source);
206 EXPECT_FALSE(source.IsConnected());
211TEST(DeviceMoveTests, MovedFromDeviceCanBeDestroyedSafely)
214 0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
220 info.
name =
"test-device";
221 info.ip =
"192.168.1.50";
223 auto mock = std::make_unique<testing::NiceMock<rpc::MockRPCCon>>();
224 ProfinetDevice source(info,
"eth0", kControllerMac, 5.0, std::move(mock));
228 auto destination = std::move(source);
259 scanner = std::make_unique<MockDcpScanner>();
272 .WillOnce(::testing::Return(responses));
300 for (
const auto& device : devices)
304 auto& blocks = responses[mac];
331 const auto responses = CreateScanResponses({
333 .mac =
"AA:BB:CC:DD:EE:01",
335 .ip =
"192.168.1.10",
338 .mac =
"AA:BB:CC:DD:EE:02",
340 .ip =
"192.168.1.11",
344 EXPECT_CALL(*mockDcpScanner,
Scan(
"eth0", 0.0))
345 .WillOnce(::testing::Return(responses));
347 EXPECT_CALL(*mockMacAddressProvider, GetMac(
"eth0"))
348 .WillOnce(::testing::Return(kMacA));
350 const auto devices =
Scan(
"eth0", 0.0, std::move(scanner), std::move(macAddressProvider));
352 ASSERT_EQ(devices.size(), responses.size());
354 for (
const auto& device : devices)
356 EXPECT_DOUBLE_EQ(GetDeviceTimeout(device), 0.0);
363 constexpr double timeoutSec = 0.1;
364 const auto responses = CreateScanResponses({{
365 .mac =
"AA:BB:CC:DD:EE:01",
367 .ip =
"192.168.1.10",
370 .mac =
"AA:BB:CC:DD:EE:02",
372 .ip =
"192.168.1.11",
375 .mac =
"AA:BB:CC:DD:EE:03",
377 .ip =
"192.168.1.12",
380 EXPECT_CALL(*mockDcpScanner,
Scan(
"eth0", timeoutSec))
381 .WillOnce(::testing::Return(responses));
383 EXPECT_CALL(*mockMacAddressProvider, GetMac(
"eth0"))
384 .WillOnce(::testing::Return(kMacA));
386 const auto devices =
Scan(
"eth0", timeoutSec, std::move(scanner), std::move(macAddressProvider));
388 ASSERT_EQ(devices.size(), responses.size());
390 for (
const auto& device : devices)
392 EXPECT_DOUBLE_EQ(GetDeviceTimeout(device), timeoutSec);
399 constexpr double timeoutSec = 30.0;
400 const auto responses = CreateScanResponses({{
401 .mac =
"AA:BB:CC:DD:EE:01",
403 .ip =
"192.168.1.10",
406 .mac =
"AA:BB:CC:DD:EE:02",
408 .ip =
"192.168.1.11",
411 .mac =
"AA:BB:CC:DD:EE:03",
413 .ip =
"192.168.1.12",
416 EXPECT_CALL(*mockDcpScanner,
Scan(
"eth0", timeoutSec))
417 .WillOnce(::testing::Return(responses));
419 EXPECT_CALL(*mockMacAddressProvider, GetMac(
"eth0"))
420 .WillOnce(::testing::Return(kMacA));
422 const auto devices =
Scan(
"eth0", timeoutSec, std::move(scanner), std::move(macAddressProvider));
424 ASSERT_EQ(devices.size(), responses.size());
426 for (
const auto& device : devices)
428 EXPECT_DOUBLE_EQ(GetDeviceTimeout(device), timeoutSec);
435 constexpr double timeoutSec = 5.0;
436 const auto responses = CreateScanResponses({{
437 .mac =
"AA:BB:CC:DD:EE:01",
439 .ip =
"192.168.1.10",
442 .mac =
"AA:BB:CC:DD:EE:02",
444 .ip =
"192.168.1.11",
446 EXPECT_CALL(*mockMacAddressProvider, GetMac(
"eth0"))
447 .WillOnce(::testing::Return(kMacA));
449 ConfigureScanResults(responses, timeoutSec);
451 const auto devices =
Scan(
"eth0", timeoutSec, std::move(scanner), std::move(macAddressProvider));
453 EXPECT_EQ(devices.size(), 2U);
459 constexpr double timeoutSec = 5.0;
461 const auto responses = CreateScanResponses(
463 {
"AA:BB:CC:DD:EE:01",
"valve",
"192.168.1.10"},
464 {
"AA:BB:CC:DD:EE:02",
"valve",
"192.168.1.11"},
466 EXPECT_CALL(*mockMacAddressProvider, GetMac(
"eth0"))
467 .WillOnce(::testing::Return(kMacA));
469 ConfigureScanResults(responses, timeoutSec);
471 const auto result =
ScanDict(
"eth0", timeoutSec, std::move(scanner), std::move(macAddressProvider));
473 ASSERT_EQ(result.size(), 2U);
475 ASSERT_NE(result.find(
"aa:bb:cc:dd:ee:01"), result.end());
477 ASSERT_NE(result.find(
"aa:bb:cc:dd:ee:02"), result.end());
479 EXPECT_EQ(result.at(
"aa:bb:cc:dd:ee:01").name,
"valve");
481 EXPECT_EQ(result.at(
"aa:bb:cc:dd:ee:02").name,
"valve");
487 constexpr double timeoutSec = 5.0;
489 const auto responses = CreateScanResponses(
491 {
"AA:BB:CC:DD:EE:01",
"",
"192.168.1.10"},
492 {
"AA:BB:CC:DD:EE:02",
"",
"192.168.1.11"},
495 EXPECT_CALL(*mockMacAddressProvider, GetMac(
"eth0"))
496 .WillOnce(::testing::Return(kMacA));
498 ConfigureScanResults(responses, timeoutSec);
500 const auto result =
ScanDict(
"eth0", timeoutSec, std::move(scanner), std::move(macAddressProvider));
502 ASSERT_EQ(result.size(), 2U);
504 ASSERT_NE(result.find(
"aa:bb:cc:dd:ee:01"), result.end());
506 ASSERT_NE(result.find(
"aa:bb:cc:dd:ee:02"), result.end());
508 EXPECT_TRUE(result.at(
"aa:bb:cc:dd:ee:01").name.empty());
510 EXPECT_TRUE(result.at(
"aa:bb:cc:dd:ee:02").name.empty());
Device not found via DCP.
Provides the MAC address of a network interface.
virtual MacAddress GetMac(const std::string &interface)=0
Get the MAC address of a network interface.
Parsed PROFINET device information from a DCP response.
std::string name
Station name.
Interface for PROFINET DCP device discovery.
virtual dcp::ResponseMap Scan(const std::string &interface, double timeoutSec)=0
Discover PROFINET devices using DCP.
Mock implementation of IDcpScanner used to test ProfinetScanner without performing real DCP network d...
MOCK_METHOD(dcp::ResponseMap, Scan,(const std::string &interface, double timeoutSec),(override))
Discover PROFINET devices using the configured mock response.
Mock implementation of IMacAddressProvider for unit tests.
MOCK_METHOD(MacAddress, GetMac,(const std::string &interface),(override))
Mock GetMac implementation.
High-level PROFINET device interface.
std::string ToString() const
Human-readable summary of this device, for logging/debugging.
std::string Mac() const
The device's MAC address.
static ProfinetDevice Discover(const std::string &identifier, const std::string &interface, double timeoutSec=10.0)
Discover a device by station name or MAC address.
void Connect()
Establish the AR.
const std::string & Ip() const
The device's IPv4 address.
static ProfinetDevice FromDcpInfo(dcp::DCPDeviceDescription info, const std::string &interface, double timeoutSec=5.0)
Wrap an already-discovered DCPDeviceDescription.
bool IsConnected() const
Whether the AR is currently established.
double timeout
Default RPC response timeout in seconds.
const std::string & Name() const
The device's station name.
Test fixture for ProfinetScanner.
MockDcpScanner * mockDcpScanner
dcp::ResponseMap CreateScanResponses(std::initializer_list< TestScanDevice > devices)
Create synthetic DCP discovery responses for scanner tests.
std::unique_ptr< MockDcpScanner > scanner
void ConfigureScanResults(const dcp::ResponseMap &responses, double timeoutSec)
Configure the mock DCP scanner to return the supplied responses.
MockMacAddressProvider * mockMacAddressProvider
static double GetDeviceTimeout(const ProfinetDevice &device)
static constexpr MacAddress kMacA
std::unique_ptr< MockMacAddressProvider > macAddressProvider
void SetUp() override
Creates the scanner with a mock DCP scanner.
High-level PROFINET device interface and device lifecycle management.
Google Mock implementation of the PROFINET alarm listener interface.
Google Mock implementation of the IRPCCon PROFINET RPC connection interface.
constexpr DcpBlockKey MakeBlockKey(std::uint16_t option, std::uint8_t suboption)
std::map< MacAddress, std::map< BlockKey, std::vector< std::uint8_t > > > ResponseMap
Result of ReadResponse(): MAC address -> parsed (option,suboption) -> payload blocks.
bool IsMacAddress(const std::string &identifier)
Whether a string parses as a MAC address.
std::vector< ProfinetDevice > Scan(const std::string &interface="eth0", double timeoutSec=3.0, std::unique_ptr< IDcpScanner > dcpScanner=nullptr, std::unique_ptr< IMacAddressProvider > macAddressProvider=nullptr)
Scan the network for PROFINET devices via DCP Identify.
TEST_F(DeviceTest, ParseMacAcceptsColonDashAndDotSeparators)
std::optional< MacAddress > ParseMac(const std::string &macStr)
Parse a MAC address string in any common separator style.
std::map< std::string, DeviceInfo > ScanDict(const std::string &interface="eth0", double timeoutSec=3.0, std::unique_ptr< IDcpScanner > dcpScanner=nullptr, std::unique_ptr< IMacAddressProvider > macAddressProvider=nullptr)
Scan the network and return a name -> DeviceInfo map.
TEST(DeviceMoveTests, MoveConstructionDisconnectsSource)
@ IpParameter
IP address, subnet mask and gateway.
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
std::array< std::uint8_t, 4 > Ip2String(const std::string &ipStr)
Parse a dotted-decimal IPv4 address string.
MacAddress String2Mac(const std::string &macStr)
Parse a colon-separated MAC address string.
@ Device
Device identification.
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
I&M0: mandatory identification data (vendor, order ID, serial number, revisions).
std::uint16_t imHardwareRevision
Hardware revision number.
std::string imSerialNumber
Device serial number (16 chars).
std::uint8_t imSwRevisionBugFix
Bug fix revision digit.
std::uint8_t swRevisionPrefix
Software revision prefix character (e.g. 'V', 'R', 'P', 'U', 'T').
std::uint8_t imSwRevisionFunctionalEnhancement
Functional enhancement revision digit.
std::uint8_t imSwRevisionInternalChange
Internal change revision digit.
std::string orderId
Manufacturer-specific order/part number (20 chars).
Complete device information summary.
std::string SerialNumber() const
Serial number from I&M0, trimmed.
std::optional< PNInM0 > im0
I&M0 identification data, if it was read successfully.
std::uint16_t HardwareRevision() const
Hardware revision from I&M0.
std::string OrderId() const
Order ID from I&M0, trimmed.
std::string SoftwareRevision() const
Software revision string from I&M0.
Test description used to construct a synthetic DCP discovery response.
std::string ip
Device IPv4 address.
std::string name
PROFINET station name.
std::string mac
Device MAC address.