EIPScanner
repository·master·Indexed 43 days ago
https://github.com/nimbuscontrols/eipscannerA free C++ implementation of an Ethernet/IP scanner for discovery and messaging with EtherNet/IP devices. It supports explicit messaging, implicit messaging (point-to-point), and discovery, with support for CIP Standard objects including Identity, Parameter, and File objects.
What's inside EIPScanner
- EIPScanner is a free implementation of an Ethernet/IP scanner written in C++. It provides tools for interacting with Ethernet/IP devices, including support for discovery, explicit messaging, and implicit messaging.
Overview of EIPScanner features and supported objects
masterEIPScanner is a free C++ implementation of an Ethernet/IP scanner. It supports the following messaging types and CIP Standard objects:
Messaging Types
- Explicit messaging
- Implicit messaging (point-to-point only)
- Discovery
CIP Standard Objects
- File Object (upload only)
- Parameter Object (read only)
- Identity Object
Vendor Specific Objects
- RA DPI Fault Object
Use the ParameterObject class to access CIP device data
masterThe
ParameterObjectclass provides a standardized way to access data and configuration from a CIP (Common Industrial Protocol) device.To use
ParameterObjecteffectively, you must first determine two things from the device:- The Instance ID of the parameters (or the total count of parameters available).
- Attribute Support: Whether the device supports full attributes (such as string descriptions and scaling) by checking the
CLASS_DESCRIPTOR.
Once initialized,
ParameterObjectinstances can be used to retrieve parameter types, names, units, and both actual and engineering values.// Example of creating a vector of ParameterObjects after discovering device capabilities std::vector<ParameterObject> parameters; parameters.reserve(paramsCount); for (int i = 0; i < paramsCount; ++i) { // Parameters are typically 1-indexed parameters.emplace_back(i + 1, allAttributes, si); }How Explicit Messaging works in EtherNet/IP
masterExplicit Messaging in EtherNet/IP is an RPC-style protocol operating over TCP/IP. To perform a service call, you must provide:
- Service Code: The specific operation to perform.
- Address (EPATH): A Common Industrial Protocol (CIP) path used to address objects. An
EPathtypically contains aClassID, anInstanceID, and optionally anAttributeID.- To call a class-level service, use
InstanceID=0.
- To call a class-level service, use
- Arguments: The data required by the service.
Example Request Structure:
- Service Code:
0x1(USINT) - Address:
ClassID=0xf0, InstanceID=0x5(EPATH) - Argument:
5(INT)
Example Response Structure:
- Service Code:
0x81(The response code is the service code ORed with0x80) - General Status:
0(SUCCESS) - Result:
6(INT)
How implicit messaging works in EIPScanner
masterImplicit messaging (IO connections) in EtherNet/IP allows a scanner and an adapter to exchange data periodically or upon change via UDP.
In EIPScanner, this is managed through two primary classes:
ConnectionManager: Responsible for establishing and managing the lifecycle of IO connections. It uses theforwardOpenmethod to initiate connections.IOConnection: Returned byforwardOpen(as a weak pointer), this object is used to send data to the adapter and register listeners for incoming data and connection closure events.
To maintain these connections, you must periodically call
ConnectionManager::handleConnections. This method handles UDP data reception, connection timeouts, and scheduled data transmission.// High-level flow ConnectionManager connectionManager; auto io = connectionManager.forwardOpen(si, parameters); if (auto ptr = io.lock()) { ptr->setDataToSend(data); ptr->setReceiveDataListener([](auto header, auto seq, auto data) { /* handle data */ }); ptr->setCloseListener([]() { /* handle close */ }); } // Crucial: periodic maintenance while (connectionManager.hasOpenConnections()) { connectionManager.handleConnections(std::chrono::milliseconds(100)); } connectionManager.forwardClose(si, io);Implement a custom log appender with LogAppenderIf
masterIf the defaultstdoutlogging is unsuitable for your application, you can redirect log output by implementing theutils::LogAppenderIfinterface. Once your custom appender is implemented, register it with the logger usingLogger::setAppender.Work with standard CIP objects using EIPScanner classes
masterEIPScanner provides specialized classes to interact with standard Common Industrial Protocol (CIP) objects. These classes abstract away the underlying CIP specifications, allowing you to work with common objects without needing to manually handle their specific data structures or attributes.
Supported standard CIP object classes include:
identity_objectparameter_objectfile_object
Configure ConnectionParameters for implicit messaging
masterTo establish an IO connection, you must populate a
ConnectionParametersobject. Note thatConnectionManagerrequires an existing EIP session (SessionInfo) to perform theForward_Openservice.Key Configuration Rules:
- Directionality: Parameters prefixed with
o2t(Originator-to-Target) define settings for the Scanner $\rightarrow$ Adapter direction. Parameters prefixed witht2o(Target-to-Originator) define settings for the Adapter $\rightarrow$ Scanner direction. - Connection Path: Must be a
std::vector<uint8_t>following the pattern:0x20,0x04,CONFIG_ASSEMBLY_ID,0x2C,OUTPUT_ASSEMBLY_ID,0x2C,INPUT_ASSEMBLY(refer to your device's EDS file). - Connection Size: The last 9 bits of
t2oNetworkConnectionParamsando2tNetworkConnectionParamsare used for the connection size. Use the|=operator to set these. - Timing:
RPI(Requested Packet Interval) andAPIvalues are specified in microseconds. - Real-Time Format: Set
o2tRealTimeFormat = trueif required by the device.
ConnectionParameters parameters; parameters.connectionPath = {0x20, 0x04, 151, 0x2C, 150, 0x2C, 100}; parameters.o2tRealTimeFormat = true; parameters.originatorVendorId = 342; parameters.originatorSerialNumber = 0x12345; // Setting size (last 9 bits) using |= parameters.t2oNetworkConnectionParams |= NetworkConnectionParams::P2P; parameters.t2oNetworkConnectionParams |= 32; // size of Assm100 parameters.o2tNetworkConnectionParams |= NetworkConnectionParams::P2P; parameters.o2tNetworkConnectionParams |= 32; // size of Assm150 parameters.o2tRPI = 1000000; // 1 second in microseconds parameters.t2oRPI = 1000000; parameters.transportTypeTrigger |= NetworkConnectionParams::CLASS1;- Directionality: Parameters prefixed with
Discover the number of parameters and attribute support
masterBefore instantiating
ParameterObjectinstances, you must query the device usingServiceCodes::GET_ATTRIBUTE_SINGLEto find the parameter count and the class descriptor.1. Get Parameter Count
Read the
MAX_INSTANCEattribute of theParameterObject::CLASS_ID. If the returned count is $N$, there are $N$ parameter instances with IDs from $1$ to $N$.2. Check for Full Attribute Support
Read the
CLASS_DESCRIPTORattribute. Check the second bit of the descriptor using theSUPPORTS_FULL_ATTRIBUTESflag to determine if metadata like scaling and descriptions are available.// 1. Read the count of parameters via MAX_INSTANCE auto response = messageRouter.sendRequest(si , ServiceCodes::GET_ATTRIBUTE_SINGLE , EPath(ParameterObject::CLASS_ID, 0, MAX_INSTANCE)); Buffer buffer(response.getData()); CipUint paramsCount; buffer >> paramsCount; // 2. Read the class descriptor to check for full attribute support response = messageRouter.sendRequest(si , ServiceCodes::GET_ATTRIBUTE_SINGLE , EPath(ParameterObject::CLASS_ID, 0, CLASS_DESCRIPTOR)); buffer = Buffer(response.getData()); CipUint descriptor; buffer >> descriptor; bool allAttributes = descriptor & SUPPORTS_FULL_ATTRIBUTES;Use the embedded Logger for simple stdout logging
masterEIPScanner includes a simple embedded logger,
utils::Logger, which outputs messages tostdout. You can control the visibility of messages by setting a global minimum log level usingLogger::setLogLevel.Important Note: Logging messages are actually printed during the destructor of the
utils::Loggerinstance. To ensure messages are printed immediately, avoid storing the logger in a long-lived variable or a class field; instead, use it as a temporary object.#include "utils/Logger.h" using eipScanner::utils::Logger; using eipScanner::utils::LogLevel; int main() { // Set the global minimum log level Logger::setLogLevel(LogLevel::INFO); // This message will be printed because INFO >= INFO Logger(LogLevel::INFO) << "You will see this message."; // This message will NOT be printed because DEBUG < INFO Logger(LogLevel::DEBUG) << "You won't see this message."; return 0; }Read and Write CIP Attributes via Explicit Messaging
masterThe most common use case for explicit communication is reading or writing CIP attributes. When performing these operations, follow these two rules:
- Address with Attribute ID: Use
cip::EPathincluding theAttributeIDyou intend to target. For example, anEPath(1, 2, 3)representsClassID=1,InstanceID=2, andAttributeID=3. - Use Standard Service Codes: Use the
cip::ServiceCodesenum to select the appropriate service code for reading or writing.
- Address with Attribute ID: Use
Enable 8-bit path segments for specific devices
masterSome devices only support 8-bit path segments. To enable this mode in EIPScanner, you must instantiate a
MessageRouterwith theMessageRouter::USE_8_BIT_PATH_SEGMENTSflag and pass that router to theConnectionManagerconstructor.This configuration ensures the
ConnectionManageruses the 8-bit path segment protocol required by those specific devices.#include "MessageRouter.h" #include "ConnectionManager.h" using eipScanner::ConnectionManager; using eipScanner::MessageRouter; int main() { // Create a MessageRouter with the USE_8_BIT_PATH_SEGMENTS flag MessageRouter::SPtr mr_ptr = std::make_shared<MessageRouter>(MessageRouter::USE_8_BIT_PATH_SEGMENTS); // Pass the router to the ConnectionManager ConnectionManager _connectionManager = ConnectionManager(mr_ptr); /* ConnectionManager now uses 8-bit path segments */ return 0; }