EIPScanner

repository·master·Indexed 43 days ago

https://github.com/nimbuscontrols/eipscanner

A 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.

Tokens
14K
Snippets
42
Records
55
Agent score
51%

What's inside EIPScanner

  1. Overview of EIPScanner features and supported objects

    master

    EIPScanner 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
  2. Use the ParameterObject class to access CIP device data

    master

    The ParameterObject class provides a standardized way to access data and configuration from a CIP (Common Industrial Protocol) device.

    To use ParameterObject effectively, you must first determine two things from the device:

    1. The Instance ID of the parameters (or the total count of parameters available).
    2. Attribute Support: Whether the device supports full attributes (such as string descriptions and scaling) by checking the CLASS_DESCRIPTOR.

    Once initialized, ParameterObject instances 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);
    }
  3. How Explicit Messaging works in EtherNet/IP

    master

    Explicit Messaging in EtherNet/IP is an RPC-style protocol operating over TCP/IP. To perform a service call, you must provide:

    1. Service Code: The specific operation to perform.
    2. Address (EPATH): A Common Industrial Protocol (CIP) path used to address objects. An EPath typically contains a ClassID, an InstanceID, and optionally an AttributeID.
      • To call a class-level service, use InstanceID=0.
    3. 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 with 0x80)
    • General Status: 0 (SUCCESS)
    • Result: 6 (INT)
  4. How implicit messaging works in EIPScanner

    master

    Implicit 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:

    1. ConnectionManager: Responsible for establishing and managing the lifecycle of IO connections. It uses the forwardOpen method to initiate connections.
    2. IOConnection: Returned by forwardOpen (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);
  5. Work with standard CIP objects using EIPScanner classes

    master

    EIPScanner 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_object
    • parameter_object
    • file_object
  6. Configure ConnectionParameters for implicit messaging

    master

    To establish an IO connection, you must populate a ConnectionParameters object. Note that ConnectionManager requires an existing EIP session (SessionInfo) to perform the Forward_Open service.

    Key Configuration Rules:

    • Directionality: Parameters prefixed with o2t (Originator-to-Target) define settings for the Scanner $\rightarrow$ Adapter direction. Parameters prefixed with t2o (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 t2oNetworkConnectionParams and o2tNetworkConnectionParams are used for the connection size. Use the |= operator to set these.
    • Timing: RPI (Requested Packet Interval) and API values are specified in microseconds.
    • Real-Time Format: Set o2tRealTimeFormat = true if 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;
  7. Discover the number of parameters and attribute support

    master

    Before instantiating ParameterObject instances, you must query the device using ServiceCodes::GET_ATTRIBUTE_SINGLE to find the parameter count and the class descriptor.

    1. Get Parameter Count

    Read the MAX_INSTANCE attribute of the ParameterObject::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_DESCRIPTOR attribute. Check the second bit of the descriptor using the SUPPORTS_FULL_ATTRIBUTES flag 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;
  8. Use the embedded Logger for simple stdout logging

    master

    EIPScanner includes a simple embedded logger, utils::Logger, which outputs messages to stdout. You can control the visibility of messages by setting a global minimum log level using Logger::setLogLevel.

    Important Note: Logging messages are actually printed during the destructor of the utils::Logger instance. 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;
    }
  9. Read and Write CIP Attributes via Explicit Messaging

    master

    The most common use case for explicit communication is reading or writing CIP attributes. When performing these operations, follow these two rules:

    1. Address with Attribute ID: Use cip::EPath including the AttributeID you intend to target. For example, an EPath(1, 2, 3) represents ClassID=1, InstanceID=2, and AttributeID=3.
    2. Use Standard Service Codes: Use the cip::ServiceCodes enum to select the appropriate service code for reading or writing.
  10. Enable 8-bit path segments for specific devices

    master

    Some devices only support 8-bit path segments. To enable this mode in EIPScanner, you must instantiate a MessageRouter with the MessageRouter::USE_8_BIT_PATH_SEGMENTS flag and pass that router to the ConnectionManager constructor.

    This configuration ensures the ConnectionManager uses 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;
    }