diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 07ca78b..14c3cc4 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -15,4 +15,7 @@ target_link_libraries(implicit_messaging EIPScanner) add_executable(parameter_object_example ParameterObjectExample.cpp) target_link_libraries(parameter_object_example EIPScanner) add_executable(discovery_example DiscoveryManagerExample.cpp) -target_link_libraries(discovery_example EIPScanner) \ No newline at end of file +target_link_libraries(discovery_example EIPScanner) + +add_executable(yaskawa_assembly_object_example vendors/yaskawa/mp3300iec/Yaskawa_AssemblyObjectExample.cpp) +target_link_libraries(yaskawa_assembly_object_example EIPScanner) diff --git a/examples/vendors/yaskawa/mp3300iec/Yaskawa_AssemblyObjectExample.cpp b/examples/vendors/yaskawa/mp3300iec/Yaskawa_AssemblyObjectExample.cpp new file mode 100644 index 0000000..8d1d9ed --- /dev/null +++ b/examples/vendors/yaskawa/mp3300iec/Yaskawa_AssemblyObjectExample.cpp @@ -0,0 +1,197 @@ +#include "cip/Types.h" +#include +#include +#include +#include +#include "ConnectionManager.h" +#include +#include "FileObject.h" +#include "fileObject/FileObjectState.h" +#include "IdentityObject.h" +#include "IOConnection.h" +#include "ParameterObject.h" +#include "SessionInfo.h" +#include "utils/Logger.h" +#include "utils/Buffer.h" + +#include "vendor/yaskawa/mp3300iec/Yaskawa_EPath.h" +#include "vendor/yaskawa/mp3300iec/Yaskawa_MessageRouter.h" +#include "vendor/yaskawa/mp3300iec/Yaskawa_MessageRouterRequest.h" + +using namespace eipScanner::cip; +using eipScanner::ConnectionManager; +using eipScanner::DiscoveryManager; +using eipScanner::IdentityObject; +using eipScanner::IOConnection; +using eipScanner::Yaskawa_MessageRouter; +using eipScanner::ParameterObject; +using eipScanner::SessionInfo; +using eipScanner::utils::Buffer; +using eipScanner::utils::Logger; +using eipScanner::utils::LogLevel; +using eipScanner::cip::connectionManager::ConnectionParameters; +using eipScanner::cip::connectionManager::NetworkConnectionParams; + +#define YASKAWA_IP_ADDR "192.168.1.2" +#define YASKAWA_PORT 0xAF12 +#define YASKAWA_VENDOR_ID 0x2c + +#define ASSEMBLY_OBJECT 0x04 + +#define YASKAWA_INPUT_ASSEMBLY_111 0x6F +#define YASKAWA_OUTPUT_ASSEMBLY_101 0x65 + +#define ASSEMBLY_INSTANCE_SIZE 128 + +#define DATA_OFFSET 100 + +/* + * fillVector - Function to fill up a vector of bytes. + * data_offset: Offset to the first byte you want to start writing + * assembly_instance_size: Size of the instance in bytes + * + * Returns a vector of bytes + */ +std::vector fillVector(int data_offset, int assembly_instance_size) +{ + std::vector data; + + for (int i = 0; i < assembly_instance_size; i++) + { + if (i < data_offset) + data.push_back(0x00); + else + data.push_back(0xDE); + } + + return data; +} + +/* + * readBuffer - Function to read a buffer into bytes + * buffer: Buffer filled with the response + * assembly_instance_size: Size of the instance in bytes + * + * Returns a vector of bytes + */ +std::vector readBuffer(Buffer buffer, int assembly_instance_size) +{ + uint8_t byte; + std::vector data; + + for (int i = 0; i < assembly_instance_size; i++) + { + buffer >> byte; + data.push_back(byte); + } + + return data; +} + +/* + * writeAssemblyObject - Writes the Assembly Instance Provided (Please note that the entire instance will be written.) + * si: SessionInfo object holding the connection Information + * assembly_instance: Integer declaring which instance to be written + * data: Vector of Bytes to be written to the Assembly Instance + * + * Returns true if successful. + */ +bool writeAssemblyObject(std::shared_ptr si, int assembly_instance, std::vector data) +{ + Yaskawa_MessageRouter messageRouter; + + auto response = messageRouter.sendRequest(si + , ServiceCodes::SET_ATTRIBUTE_SINGLE + , Yaskawa_EPath(ASSEMBLY_OBJECT, assembly_instance, 0x03) + , data); + + if (response.getGeneralStatusCode() != GeneralStatusCodes::SUCCESS) { + Logger(LogLevel::ERROR) << "Failed to write assembly object"; + logGeneralAndAdditionalStatus(response); + return 0; + } + + Logger(LogLevel::INFO) << "The device has written successfully."; + return 1; +} + +/* + * readAssemblyObject - Reads the Assembly Instance Provided (Please note that the entire instance will be read.) + * si: SessionInfo object holding the connection Information + * assembly_instance: Integer declaring which instance to be read + * + * Returns a vector of bytes that contains the response or nothing if there was an error + */ +std::vector readAssemblyObject(std::shared_ptr si, int assembly_instance) +{ + Yaskawa_MessageRouter messageRouter; + std::vector data_read; + uint8_t firstByte; + int num_bytes; + + auto response = messageRouter.sendRequest(si + , ServiceCodes::GET_ATTRIBUTE_SINGLE + , Yaskawa_EPath(ASSEMBLY_OBJECT, assembly_instance, 0x03)); + + if (response.getGeneralStatusCode() != GeneralStatusCodes::SUCCESS) { + Logger(LogLevel::ERROR) << "Failed to read parameters"; + logGeneralAndAdditionalStatus(response); + return data_read; + } + + Buffer buffer(response.getData()); + + num_bytes = 4 * sizeof (buffer); + data_read = readBuffer(buffer, num_bytes); + firstByte = data_read[0]; + + // converting the byte to uint16_t to properly print with the Logger + Logger(LogLevel::INFO) << "The device has " << num_bytes << " bytes. The first byte contains: " << (uint16_t)firstByte; + + return data_read; +} + +// Test to read and write assembly objects on the adapter +int main() { + + Logger::setLogLevel(LogLevel::DEBUG); + std::vector data_read; + bool success; + + // Connect to the adapter with the given ip address and port number + auto si = std::make_shared(YASKAWA_IP_ADDR, YASKAWA_PORT); + + // Data to be written -- In this example we just fill it with junk + std::vector data_write = fillVector(0x00, ASSEMBLY_INSTANCE_SIZE); + + // Assembly Object - Perform a read on the output assembly, and then a read and a write on the input assembly + data_read = readAssemblyObject(si, YASKAWA_OUTPUT_ASSEMBLY_101); + data_read = readAssemblyObject(si, YASKAWA_INPUT_ASSEMBLY_111); + success = writeAssemblyObject(si, YASKAWA_INPUT_ASSEMBLY_111, data_write); + + return 0; +} + +#if 0 +// Test to confirm you are connected to a Yaskawa Device +int main() { + + Logger::setLogLevel(LogLevel::DEBUG); + + DiscoveryManager discoveryManager(YASKAWA_IP_ADDR, YASKAWA_PORT, std::chrono::seconds(1)); + auto devices = discoveryManager.discover(); + + for (auto& device : devices) { + if (device.identityObject.getVendorId() == YASKAWA_VENDOR_ID) + { + Logger(LogLevel::INFO) << "Discovered YASKAWA device: " + << device.identityObject.getProductName() + << " with address " << device.socketAddress.toString(); + } + } + + + return 0; +} +#endif + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1eb60fe..f8677db 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,6 +38,9 @@ set(SOURCE_FILES vendor/ra/powerFlex525/DPIFaultObject.cpp vendor/ra/powerFlex525/DPIFaultCode.cpp vendor/ra/powerFlex525/DPIFaultParameter.cpp + vendor/yaskawa/mp3300iec/Yaskawa_EPath.cpp + vendor/yaskawa/mp3300iec/Yaskawa_MessageRouter.cpp + vendor/yaskawa/mp3300iec/Yaskawa_MessageRouterRequest.cpp BaseObject.cpp ConnectionManager.cpp diff --git a/src/vendor/yaskawa/mp3300iec/Yaskawa_EPath.cpp b/src/vendor/yaskawa/mp3300iec/Yaskawa_EPath.cpp new file mode 100644 index 0000000..db26946 --- /dev/null +++ b/src/vendor/yaskawa/mp3300iec/Yaskawa_EPath.cpp @@ -0,0 +1,165 @@ +#include +#include "utils/Buffer.h" +#include "Yaskawa_EPath.h" + +namespace eipScanner { +namespace cip { + using utils::Buffer; + + enum class EPathSegmentTypes : CipUsint { + CLASS_8_BITS = 0x20, + CLASS_16_BITS = 0x21, + INSTANCE_8_BITS = 0x24, + INSTANCE_16_BITS = 0x25, + ATTRIBUTE_8_BITS = 0x30, + ATTRIBUTE_16_BITS = 0x31, + }; + + Yaskawa_EPath::Yaskawa_EPath() + : _classId{0} + , _objectId{0} + , _attributeId{0} + , _size{0}{ + + } + Yaskawa_EPath::Yaskawa_EPath(CipUsint classId) + : _classId{classId} + , _objectId{0} + , _attributeId{0} + , _size{1} { + + } + + Yaskawa_EPath::Yaskawa_EPath(CipUsint classId, CipUsint objectId) + : _classId{classId} + , _objectId{objectId} + , _attributeId{0} + , _size{2} { + + } + + Yaskawa_EPath::Yaskawa_EPath(CipUsint classId, CipUsint objectId, CipUsint attributeId) + : _classId{classId} + , _objectId{objectId} + , _attributeId{attributeId} + , _size{3} { + } + + std::vector Yaskawa_EPath::packPaddedPath() const { + Buffer buffer(_size*2); + + auto classSegment = static_cast(EPathSegmentTypes::CLASS_8_BITS); + buffer << classSegment << _classId; + + if (_size > 1) { + auto instanceSegment = static_cast(EPathSegmentTypes::INSTANCE_8_BITS); + buffer << instanceSegment << _objectId; + + if (_size > 2) { + auto attributeSegment = static_cast(EPathSegmentTypes::ATTRIBUTE_8_BITS); + buffer << attributeSegment << _attributeId; + } + } + + return buffer.data(); + } + + + CipUsint Yaskawa_EPath::getClassId() const { + return _classId; + } + + CipUsint Yaskawa_EPath::getObjectId() const { + return _objectId; + } + + CipUsint Yaskawa_EPath::getAttributeId() const { + return _attributeId; + } + + CipUsint Yaskawa_EPath::getSizeInWords() const { + return _size; + } + + std::string Yaskawa_EPath::toString() const { + std::string msg = "[classId=" + std::to_string(_classId); + if (_size > 1) { + msg += " objectId=" + std::to_string(_objectId); + if (_size > 2) { + msg += " attributeId=" + std::to_string(_attributeId); + } + } + + msg += "]"; + return msg; + } + + void Yaskawa_EPath::expandPaddedPath(const std::vector &data) { + Buffer buffer(data); + + _classId = 0; + _objectId = 0; + _attributeId = 0; + _size = 0; + + for (int i = 0; i < data.size() && !buffer.empty(); ++i) { + EPathSegmentTypes segmentType; + CipUsint ignore = 0; + CipUsint byte; + CipUint word; + buffer >> reinterpret_cast(segmentType); + switch (segmentType) { + case EPathSegmentTypes::CLASS_8_BITS: + buffer >> byte; + _classId = byte; + break; + case EPathSegmentTypes::CLASS_16_BITS: + buffer >> ignore >> word; + _classId = word; + break; + case EPathSegmentTypes::INSTANCE_8_BITS: + buffer >> byte; + _objectId = byte; + break; + case EPathSegmentTypes::INSTANCE_16_BITS: + buffer >> ignore >> word; + _objectId = word; + break; + case EPathSegmentTypes::ATTRIBUTE_8_BITS: + buffer >> byte; + _attributeId = byte; + break; + case EPathSegmentTypes::ATTRIBUTE_16_BITS: + buffer >> ignore >> word; + _attributeId = word; + break; + default: + throw std::runtime_error("Unknown EPATH segment =" + std::to_string(static_cast(segmentType))); + } + } + + if (!buffer.isValid()) { + throw std::runtime_error("Wrong EPATH format"); + } + + if (_classId > 0) { + _size++; + + if (_objectId > 0) { + _size++; + + if (_attributeId > 0) { + _size++; + } + } + } + } + + bool Yaskawa_EPath::operator==(const Yaskawa_EPath &other) const { + return _size == other._size + &&_classId == other._classId + && _objectId == other._objectId + && _attributeId == other._attributeId; + } +} +} diff --git a/src/vendor/yaskawa/mp3300iec/Yaskawa_EPath.h b/src/vendor/yaskawa/mp3300iec/Yaskawa_EPath.h new file mode 100644 index 0000000..6fbf035 --- /dev/null +++ b/src/vendor/yaskawa/mp3300iec/Yaskawa_EPath.h @@ -0,0 +1,42 @@ +#ifndef EIPSCANNER_YASKAWA_EPATH_H +#define EIPSCANNER_YASKAWA_EPATH_H + +#include +#include +#include + +#include "cip/Types.h" + + +namespace eipScanner { +namespace cip { + class Yaskawa_EPath { + public: + Yaskawa_EPath(); + explicit Yaskawa_EPath(CipUsint classId); + Yaskawa_EPath(CipUsint classId, CipUsint objectId); + Yaskawa_EPath(CipUsint classId, CipUsint objectId, CipUsint attributeId); + std::vector packPaddedPath() const; + void expandPaddedPath(const std::vector& data); + + CipUsint getClassId() const; + CipUsint getObjectId() const; + CipUsint getAttributeId() const; + + CipUsint getSizeInWords() const; + + std::string toString() const; + bool operator==(const Yaskawa_EPath& other) const; + + private: + CipUsint _classId; + CipUsint _objectId; + CipUsint _attributeId; + + CipUsint _size; + }; +} +} + + +#endif // EIPSCANNER_YASKAWA_EPATH_H diff --git a/src/vendor/yaskawa/mp3300iec/Yaskawa_MessageRouter.cpp b/src/vendor/yaskawa/mp3300iec/Yaskawa_MessageRouter.cpp new file mode 100644 index 0000000..f966901 --- /dev/null +++ b/src/vendor/yaskawa/mp3300iec/Yaskawa_MessageRouter.cpp @@ -0,0 +1,82 @@ + +#include +#include "eip/EncapsPacketFactory.h" +#include "utils/Buffer.h" +#include "Yaskawa_MessageRouter.h" +#include "Yaskawa_MessageRouterRequest.h" +#include "cip/MessageRouterResponse.h" +#include "eip/CommonPacketItemFactory.h" +#include "eip/CommonPacket.h" +#include "utils/Buffer.h" +#include "utils/Logger.h" + +namespace eipScanner { + using namespace cip; + using namespace utils; + using eip::CommonPacketItemFactory; + using eip::CommonPacket; + using eip::CommonPacketItem; + using eip::EncapsPacket; + using eip::EncapsPacketFactory; + + Yaskawa_MessageRouter::Yaskawa_MessageRouter() = default; + + Yaskawa_MessageRouter::~Yaskawa_MessageRouter() = default; + + MessageRouterResponse + Yaskawa_MessageRouter::sendRequest(SessionInfoIf::SPtr si, CipUsint service, const Yaskawa_EPath &path, + const std::vector &data) const{ + return this->sendRequest(si, service, path, data, {}); + } + + MessageRouterResponse + Yaskawa_MessageRouter::sendRequest(SessionInfoIf::SPtr si, CipUsint service, const Yaskawa_EPath &path, + const std::vector &data, + const std::vector& additionalPacketItems) const{ + assert(si); + + Logger(LogLevel::INFO) << "Send request: service=0x" << std::hex << static_cast(service) + << " epath=" << path.toString(); + + Yaskawa_MessageRouterRequest request{service, path, data}; + + CommonPacketItemFactory commonPacketItemFactory; + CommonPacket commonPacket; + commonPacket << commonPacketItemFactory.createNullAddressItem(); + commonPacket << commonPacketItemFactory.createUnconnectedDataItem(request.pack()); + + for(auto& item : additionalPacketItems) { + commonPacket << item; + } + + auto packetToSend = EncapsPacketFactory() + .createSendRRDataPacket(si->getSessionHandle(), 0, commonPacket.pack()); + + auto receivedPacket = si->sendAndReceive(packetToSend); + + Buffer buffer(receivedPacket.getData()); + cip::CipUdint interfaceHandle = 0; + cip::CipUint timeout = 0; + std::vector receivedData(receivedPacket.getData().size() - 6); + + buffer >> interfaceHandle >> timeout >> receivedData; + commonPacket.expand(receivedData); + + MessageRouterResponse response; + const CommonPacketItem::Vec &items = commonPacket.getItems(); + + response.expand(items.at(1).getData()); + if (items.size() > 2) { + response.setAdditionalPacketItems( + CommonPacketItem::Vec(items.begin() + 2, items.end())); + } + + + return response; + } + + MessageRouterResponse + Yaskawa_MessageRouter::sendRequest(SessionInfoIf::SPtr si, CipUsint service, const Yaskawa_EPath &path) const{ + return this->sendRequest(si, service, path, {}, {}); + } +} diff --git a/src/vendor/yaskawa/mp3300iec/Yaskawa_MessageRouter.h b/src/vendor/yaskawa/mp3300iec/Yaskawa_MessageRouter.h new file mode 100644 index 0000000..40faa66 --- /dev/null +++ b/src/vendor/yaskawa/mp3300iec/Yaskawa_MessageRouter.h @@ -0,0 +1,75 @@ +#ifndef EIPSCANNER_YASKAWA_MESSAGEROUTER_H +#define EIPSCANNER_YASKAWA_MESSAGEROUTER_H + +#include +#include "Yaskawa_EPath.h" +#include "cip/Services.h" +#include "cip/MessageRouterResponse.h" +#include "eip/CommonPacketItem.h" +#include "SessionInfo.h" + +namespace eipScanner { + /** + * @class MessageRouter + * + * @brief Implements the explicit messaging with EIP adapter + */ + class Yaskawa_MessageRouter { + public: + using SPtr = std::shared_ptr; + + /** + * @brief Default constructor + */ + Yaskawa_MessageRouter(); + + /** + * @brief Default destructor + */ + virtual ~Yaskawa_MessageRouter(); + + /** + * @brief Sends an explicit requests to the EIP adapter by calling a CIP service + * @param si the EIP session with the adapter + * @param service the service code (for standard codes see eipScanner::cip::ServiceCodes) + * @param path the path to an element in Object Model that provides the called service + * @param data the encoded arguments of the service + * @param additionalPacketItems (needed only for eipScanner::ConnectionManager) + * @return the received response from the EIP adapter + * @throw std::runtime_error + * @throw std::system_error + */ + virtual cip::MessageRouterResponse sendRequest(SessionInfoIf::SPtr si, cip::CipUsint service, + const cip::Yaskawa_EPath& path, const std::vector& data, + const std::vector& additionalPacketItems) const; + + /** + * @brief Sends an explicit requests to the EIP adapter by calling a CIP service + * @param si the EIP session with the adapter + * @param service the service code (for standard codes see eipScanner::cip::ServiceCodes) + * @param path the path to an element in Object Model that provides the called service + * @param data the encoded arguments of the service + * @return the received response from the EIP adapter + * @throw std::runtime_error + * @throw std::system_error + */ + virtual cip::MessageRouterResponse sendRequest(SessionInfoIf::SPtr si, cip::CipUsint service, + const cip::Yaskawa_EPath& path, const std::vector& data) const; + + /** + * @brief Sends an explicit requests to the EIP adapter by calling a CIP service + * @param si the EIP session with the adapter + * @param service the service code (for standard codes see eipScanner::cip::ServiceCodes) + * @param path the path to an element in Object Model that provides the called service + * @return the received response from the EIP adapter + * @throw std::runtime_error + * @throw std::system_error + */ + virtual cip::MessageRouterResponse sendRequest(SessionInfoIf::SPtr si, cip::CipUsint service, + const cip::Yaskawa_EPath& path) const; + + }; +} + + +#endif // EIPSCANNER_YASKAWA_MESSAGEROUTER_H diff --git a/src/vendor/yaskawa/mp3300iec/Yaskawa_MessageRouterRequest.cpp b/src/vendor/yaskawa/mp3300iec/Yaskawa_MessageRouterRequest.cpp new file mode 100644 index 0000000..8ac06b1 --- /dev/null +++ b/src/vendor/yaskawa/mp3300iec/Yaskawa_MessageRouterRequest.cpp @@ -0,0 +1,28 @@ +#include +#include "Yaskawa_MessageRouterRequest.h" +#include "Yaskawa_EPath.h" + +namespace eipScanner { +namespace cip { + using utils::Buffer; + + Yaskawa_MessageRouterRequest::Yaskawa_MessageRouterRequest(CipUsint serviceCode, + const Yaskawa_EPath& ePath, const std::vector data) + : _serviceCode{serviceCode} + , _ePath{ePath} + , _data(data) { + } + + Yaskawa_MessageRouterRequest::~Yaskawa_MessageRouterRequest() = default; + + std::vector Yaskawa_MessageRouterRequest::pack() const { + Buffer buffer; + buffer << _serviceCode + << _ePath.getSizeInWords() + << _ePath.packPaddedPath() + << _data; + + return buffer.data(); + } +} +} diff --git a/src/vendor/yaskawa/mp3300iec/Yaskawa_MessageRouterRequest.h b/src/vendor/yaskawa/mp3300iec/Yaskawa_MessageRouterRequest.h new file mode 100644 index 0000000..2f2c640 --- /dev/null +++ b/src/vendor/yaskawa/mp3300iec/Yaskawa_MessageRouterRequest.h @@ -0,0 +1,28 @@ +#ifndef EIPSCANNER_YASKAWA_MESSAGEROUTERREQUEST_H +#define EIPSCANNER_YASKAWA_MESSAGEROUTERREQUEST_H + +#include +#include + +#include "cip/Services.h" +#include "Yaskawa_EPath.h" + +namespace eipScanner { +namespace cip { + class Yaskawa_MessageRouterRequest { + public: + Yaskawa_MessageRouterRequest(CipUsint serviceCode, const Yaskawa_EPath& ePath, const std::vector data); + ~Yaskawa_MessageRouterRequest(); + + std::vector pack() const; + private: + CipUsint _serviceCode; + Yaskawa_EPath _ePath; + std::vector _data; + }; + +} +} + + +#endif // EIPSCANNER_YASKAWA_MESSAGEROUTERREQUEST_H