├── .gitignore ├── ChangeLog ├── CoDeSys_EIP.library ├── Examples ├── Fanuc │ ├── Fanuc_RPi.project │ ├── Implicit Messaging │ │ ├── Images │ │ │ ├── CoDeSys_1_EIP.png │ │ │ ├── CoDeSys_2_EIP_Adapter_General.png │ │ │ ├── CoDeSys_2_EIP_Scanner_General.png │ │ │ ├── CoDeSys_3_EIP_Adapter_Input.png │ │ │ ├── CoDeSys_3_EIP_Adapter_Output.png │ │ │ ├── CoDeSys_3_EIP_Scanner_Connection.png │ │ │ ├── Fanuc_1_EIP.png │ │ │ ├── Fanuc_2_EIP_Adapter_Connection.png │ │ │ ├── Fanuc_2_EIP_Scanner_Connection.png │ │ │ ├── Fanuc_3_EIP_Adapter_Setup.png │ │ │ ├── Fanuc_3_EIP_Scanner_Setup.png │ │ │ ├── Fanuc_4_EIP_Adapter_Connection.png │ │ │ ├── Fanuc_4_EIP_Scanner_Connection.png │ │ │ ├── Fanuc_5_EIP_DIO.png │ │ │ ├── Fanuc_6_EIP_DI.png │ │ │ └── Fanuc_6_EIP_DO.png │ │ └── README.md │ ├── Literature │ │ └── EtherNetIP_Setup_and_Operations.pdf │ ├── README.md │ └── Struct │ │ ├── stCartesianPosition.txt │ │ ├── stJointPosition.txt │ │ └── stStringRegister.txt └── Rockwell │ ├── Implicit Messaging │ ├── CODESYS EtherNetIP_Configuration_of_Rockwell_Adapter.pdf │ ├── Images │ │ ├── CoDeSys_1_EIP.png │ │ ├── CoDeSys_2_EIP_Adapter_General.png │ │ ├── CoDeSys_3_EIP_Adapter_Input.png │ │ ├── CoDeSys_3_EIP_Adapter_Output.png │ │ ├── Rockwell_1_EIP.png │ │ ├── Rockwell_1_EIP_Module.png │ │ └── Rockwell_3_EIP_Scanner_General.png │ └── README.md │ ├── Literature │ ├── CIP.pdf │ ├── CIP_Vol1_3.3.pdf │ ├── CIP_Vol2_1.4.pdf │ ├── CIP_Vol7_1.7.pdf │ ├── Controller_CIP.pdf │ ├── DataAccess_1756-PM020F-EN-P.pdf │ ├── DataAccess_1756-RM005A-EN-E.pdf │ ├── Developer_Guide.pdf │ ├── GeneralInstructions_1756-RM003_-EN-P.pdf │ └── TypeEncode_CIPRW.pdf │ ├── Nonblocking_Loop.project │ ├── Read-Write_Tags_RPi.project │ ├── Set-Get_Attribute_RPi.project │ └── Struct │ ├── README.md │ ├── STRING12.L5X │ ├── STRING24.L5X │ ├── STRING25.L5X │ ├── stBit.txt │ ├── stComplex.txt │ ├── stFiveStrings.txt │ ├── stMixDatatype.txt │ ├── stMyEvent.txt │ ├── stString.txt │ ├── stString24.txt │ ├── stString25.txt │ ├── stTimer.txt │ ├── udtBit.L5X │ ├── udtComplex.L5X │ ├── udtFiveStrings.L5X │ ├── udtMixDatatype.L5X │ ├── udtMyEvent.L5X │ ├── udtSTRING.png │ └── udtTIMER.png ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Test/UnitTests.project 2 | *.~u 3 | *.bootinfo 4 | *.bootinfo_guids 5 | *.compileinfo 6 | *.opt 7 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | v1.0.4.9: 2 | * handles autoreconnect on physical ethernet disconnect 3 | 4 | v1.0.4.8: 5 | * under stCipService, changed class to cipClass due to keyword warning. 6 | * updated example projects 7 | 8 | v1.0.4.7: 9 | * added stDeviceStatus to list identity status (thanks Canaan) 10 | 11 | v1.0.4.6: 12 | * in gvcParameters: 13 | * created parameter uiAttributeDataSize for stCipServiceData.attributeData 14 | * created parameter uiRouteSize for stRoute.route 15 | * changed tTcpClientRetry to udiTcpClientRetry and tCipRequestTimeout to uiCipRequestTimeout 16 | 17 | v1.0.4.5: 18 | * moved init variables from FB_init into gvcParameters so end users can adjust default values directly from Library Manager 19 | 20 | v1.0.4.4: 21 | * increased stCipServiceData.attributeData from 256 to 512. 22 | * updated documentation 23 | 24 | v1.0.4.3: 25 | * changes to list identity 26 | * removed and replaced stListIdentityReq with stEipEncapsulationHeader for request 27 | * exposed device socket data to end user, useful for obtaining device's true IP address if behind a network address translator 28 | * updated various aux functions 29 | 30 | v1.0.4.2: 31 | * fixed bGetPlcTime bug from v1.0.4.0 32 | * standardized error reporting across all methods 33 | 34 | v1.0.4.1: 35 | * reorganized switch cases in main to prioritize events 36 | * updated bGenericDevice to reflect changes 37 | 38 | v1.0.4.0: 39 | * implemented EIP generic service and get/set attribute all 40 | * optimized service templates resulting in minor impact to existing end user implementation 41 | * combined templates stAttributeAll/stAttributeSingle/stAttributeList into stCipService 42 | * updated example projects 43 | * fixes #8 44 | 45 | v1.0.3.1: 46 | * removed testing artifacts: `_bBuildConnectionPath_FANUC()` 47 | * added `stRoute` just in case end user need to specify non-standard custom route 48 | 49 | v1.0.3.0: 50 | * implemented unconnected messaging (Send RR Data) for all read/write and set/get 51 | * optimized forward open/close to remove `_uiCalcConnPathSize()` 52 | * renamed `_bCipWriteSuccess` to `_bCipRequestSuccess`, removed `bOverrideCheck`, and added `bUnconnectedMessaging` 53 | * grouped send rr/unit data into `SendData` folder 54 | * updated Fanuc example 55 | * Fixes #7 56 | 57 | v1.0.2.0: 58 | * implemented large forward open 59 | * updated enumerations and global variable lists 60 | * Fixes #4 61 | 62 | v1.0.1.1: 63 | * added pointer checks to bRead/bWrite, and the set/get attributes to make sure pointer is valid 64 | * added eCipStatusGeneral, gvlDeviceStates, _sGetDeviceState, sDeviceState 65 | * updated example projects to use latest version of library 66 | * updated enumerations and used them in code 67 | * Fixes #3, #5 68 | 69 | v1.0.1.0: 70 | * PLC is renamed to Device to open up project scope 71 | * restructured set/get attributes all to be more flexible 72 | * added `psId` to to bRead/bWrite, and the set/get attributes 73 | * added enumerations to start cleaning up main 74 | * added error checks into methods 75 | * added PlcAudit features and grouped methods into folders 76 | * added Fanuc example (work in progress) 77 | * Fixes #2 78 | 79 | v1.0.0.2: 80 | * first implementation of set/get attributes list 81 | * started on set/get attribute single 82 | * minor optimization in pointers 83 | * updated enumerations and used them in code 84 | * opened up issues for enhancements 85 | 86 | v1.0.0.1: 87 | * changed bRead/bWrite to use pointer for tag and renamed its `uiUdtSize` to `uiSize` 88 | * added gvlCipGeneralStatus, eCipConnManager, eCipServices, eEipServices to start cleaning up main 89 | * renamed `dataTypeExtended` to `structIdSize` 90 | * Fixes #1 91 | 92 | v1.0.0.0: 93 | * first release -------------------------------------------------------------------------------- /CoDeSys_EIP.library: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/CoDeSys_EIP.library -------------------------------------------------------------------------------- /Examples/Fanuc/Fanuc_RPi.project: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Fanuc_RPi.project -------------------------------------------------------------------------------- /Examples/Fanuc/Implicit Messaging/Images/CoDeSys_1_EIP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Implicit Messaging/Images/CoDeSys_1_EIP.png -------------------------------------------------------------------------------- /Examples/Fanuc/Implicit Messaging/Images/CoDeSys_2_EIP_Adapter_General.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Implicit Messaging/Images/CoDeSys_2_EIP_Adapter_General.png -------------------------------------------------------------------------------- /Examples/Fanuc/Implicit Messaging/Images/CoDeSys_2_EIP_Scanner_General.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Implicit Messaging/Images/CoDeSys_2_EIP_Scanner_General.png -------------------------------------------------------------------------------- /Examples/Fanuc/Implicit Messaging/Images/CoDeSys_3_EIP_Adapter_Input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Implicit Messaging/Images/CoDeSys_3_EIP_Adapter_Input.png -------------------------------------------------------------------------------- /Examples/Fanuc/Implicit Messaging/Images/CoDeSys_3_EIP_Adapter_Output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Implicit Messaging/Images/CoDeSys_3_EIP_Adapter_Output.png -------------------------------------------------------------------------------- /Examples/Fanuc/Implicit Messaging/Images/CoDeSys_3_EIP_Scanner_Connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Implicit Messaging/Images/CoDeSys_3_EIP_Scanner_Connection.png -------------------------------------------------------------------------------- /Examples/Fanuc/Implicit Messaging/Images/Fanuc_1_EIP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Implicit Messaging/Images/Fanuc_1_EIP.png -------------------------------------------------------------------------------- /Examples/Fanuc/Implicit Messaging/Images/Fanuc_2_EIP_Adapter_Connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Implicit Messaging/Images/Fanuc_2_EIP_Adapter_Connection.png -------------------------------------------------------------------------------- /Examples/Fanuc/Implicit Messaging/Images/Fanuc_2_EIP_Scanner_Connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Implicit Messaging/Images/Fanuc_2_EIP_Scanner_Connection.png -------------------------------------------------------------------------------- /Examples/Fanuc/Implicit Messaging/Images/Fanuc_3_EIP_Adapter_Setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Implicit Messaging/Images/Fanuc_3_EIP_Adapter_Setup.png -------------------------------------------------------------------------------- /Examples/Fanuc/Implicit Messaging/Images/Fanuc_3_EIP_Scanner_Setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Implicit Messaging/Images/Fanuc_3_EIP_Scanner_Setup.png -------------------------------------------------------------------------------- /Examples/Fanuc/Implicit Messaging/Images/Fanuc_4_EIP_Adapter_Connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Implicit Messaging/Images/Fanuc_4_EIP_Adapter_Connection.png -------------------------------------------------------------------------------- /Examples/Fanuc/Implicit Messaging/Images/Fanuc_4_EIP_Scanner_Connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Implicit Messaging/Images/Fanuc_4_EIP_Scanner_Connection.png -------------------------------------------------------------------------------- /Examples/Fanuc/Implicit Messaging/Images/Fanuc_5_EIP_DIO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Implicit Messaging/Images/Fanuc_5_EIP_DIO.png -------------------------------------------------------------------------------- /Examples/Fanuc/Implicit Messaging/Images/Fanuc_6_EIP_DI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Implicit Messaging/Images/Fanuc_6_EIP_DI.png -------------------------------------------------------------------------------- /Examples/Fanuc/Implicit Messaging/Images/Fanuc_6_EIP_DO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Implicit Messaging/Images/Fanuc_6_EIP_DO.png -------------------------------------------------------------------------------- /Examples/Fanuc/Implicit Messaging/README.md: -------------------------------------------------------------------------------- 1 | # Set up Discrete I/O Communication 2 | Quick tutorial on how to set up I/O between your CoDeSys controller and the Fanuc robot controller using implicit messaging 3 | 4 | ### CoDeSys Scanner <=> Fanuc Adapter 5 | 6 | #### CoDeSys 7 | 1) ![CoDeSys_1_EIP](Images/CoDeSys_1_EIP.png) 8 | 1) Right click on your controller device (e.g. CODESYS Control for Raspberry Pi MC SL) 9 | 1) Select `Add device` 10 | 2) Expand `Ethernet Adapter` and select `Ethernet` 11 | 3) Click `Add Device` 12 | 2) Double click the newly added `Ethernet (EtherNet)` device 13 | 1) In the `General` tab, press `...` to select the controller's Ethernet network interface 14 | 2) ![CoDeSys_2_EIP_Scanner_General](Images/CoDeSys_2_EIP_Scanner_General.png) 15 | 1) Right click the newly added `Ethernet (Ethernet)` device 16 | 1) Select `Add device` 17 | 2) Expand `EtherNet/IP` -> `EtherNet/IP Scanner` and select `EtherNet/IP Scanner` 18 | 3) Click `Add Device` 19 | 2) Right click the newly added `Ethernet_IP_Scanner (EtherNet/IP Scanner)` device 20 | 1) Select `Add device` 21 | 2) Select `Generic EtherNet/IP Device` 22 | 3) Click `Add Device` 23 | 3) Double click on the newly added `Generic_EtherNet_IP_device (Generic EtherNet/IP Device)` device 24 | 1) In the `General` tab, enter the IP address of the robot controller 25 | 3) ![CoDeSys_3_EIP_Scanner_Connection](Images/CoDeSys_3_EIP_Scanner_Connection.png) 26 | 1) Select the `Connections` tab and click `Add Connection...` 27 | 2) Check `Configuration assembly` and enter `64` for Instance ID. This is hex value for `100` in decimal 28 | 3) Check `Consuming assembly (O-->T)` and enter `98` for Instance ID. This is hex value for `152` in decimal 29 | 1) **NOTE:** Example uses slot 2 on robot controller. Change value to `97` hex / `151` decimal for slot 1, or `99` hex / `153` decimal for slot 3, etc. 30 | 4) Check `Configuration assembly` and enter `66` for Instance ID. This is hex value for `102` in decimal 31 | 1) **NOTE:** Example uses slot 2 on robot controller. Change value to `65` hex / `101` decimal for slot 1, or `67` hex / `103` decimal for slot 3, etc. 32 | 5) Modify: 33 | 1) The RPI if needed. Default is `10ms` 34 | 2) `O-->T size (bytes)` is CoDeSys's output to Fanuc's input. Example uses 2 words, which can control 32 Fanuc input bits 35 | 3) `T-->O size (bytes)` is Fanuc's output to CoDeSys's input. Example uses 2 words, which can control 32 CoDeSys input bits 36 | 4) `Connection type` to `Point to Point` 37 | 5) `Connection Priority` to `Scheduled`. 38 | 6) `Fixed/Variable` to `Fixed` 39 | 7) `Transfer format` of `Scanner to Target (Output)` to `32-bit run/idle`, and `Transfer format` of `Target to Scanner (Input)` to `Pure data` 40 | 8) Click `OK` 41 | 42 | #### Fanuc 43 | 1) ![Fanuc_1_EIP](Images/Fanuc_1_EIP.png) 44 | 1) On the teach pendant, press `Menu` 45 | 2) Use arrow key and navigate to `5 I/O` -> `EtherNet/IP` 46 | 2) ![Fanuc_2_EIP_Adapter_Connection](Images/Fanuc_2_EIP_Adapter_Connection.png) 47 | 1) Select the slot number for usage 48 | 1) Optional: press enter to change the default description of `ConnectionX`. Example uses slot 2 49 | 2) Make sure TYP column is set to `ADP` 50 | 3) Highlight the device under the Description column and press `CONFIG` or `F4`. 51 | 1) Make sure Enable is set to `FALSE` to modify values 52 | 3) ![Fanuc_3_EIP_Adapter_Setup](Images/Fanuc_3_EIP_Adapter_Setup.png) 53 | 1) Change `Input size (words)` to 2, and repeat the same for `Output size (words)`. Example is to control 32 bits (4 bytes / 2 words), but you can adjust to fit your application 54 | 2) Use `[CHOICE]` or `F4` to adjust `Alarm Severity` if needed. 55 | 1) Default is `WARN`, `PAUSE` or `STOP` will stop the robot motion if EtherNet/IP fault occurs 56 | 3) Press `PREV` or `F3` 57 | 4) ![Fanuc_4_EIP_Adapter_Connection](Images/Fanuc_4_EIP_Adapter_Connection.png) 58 | 1) Change Enable from `FALSE` to `TRUE` 59 | 5) ![Fanuc_5_EIP_DIO](Images/Fanuc_5_EIP_DIO.png) 60 | 1) On the teach pendant, press `Menu` 61 | 2) Use arrow key and navigate to `5 I/O` -> `3 Digital`. Example controls bits, but you can change to `5 Group`. 62 | 6) ![DO](Images/Fanuc_6_EIP_DO.png) 63 | 1) Start with `I/O Digital Out`. If you do not see this screen, press `IN/OUT` or `F3` to switch screen 64 | 2) Press `CONFIG` or F2 65 | 3) Change range, example uses DO[1- 32]. 66 | 4) Set `RACK` to 89, which is EtherNet/IP 67 | 5) Set SLOT, example uses slot 2. 68 | 6) Set START, example uses full range so start index is 1 69 | 7) Press `IN/OUT` for `F3` to switch to `I/O Digital In` and repeat the steps from above 70 | 7) Power cycle the controller to apply settings 71 | 72 | ### Fanuc Scanner <=> CoDeSys Adapter 73 | 74 | #### Fanuc 75 | 1) ![Fanuc_1_EIP](Images/Fanuc_1_EIP.png) 76 | 1) On the teach pendant, press `Menu` 77 | 2) Use arrow key and navigate to `5 I/O` -> `EtherNet/IP` 78 | 2) ![Fanuc_2_EIP_Scanner_Connection](Images/Fanuc_2_EIP_Scanner_Connection.png) 79 | 1) Select the slot number that is available for usage 80 | 1) Optional: press enter to change the default description of `ConnectionX`. Example uses slot 2 81 | 2) Make sure TYP column is set to `SCN` 82 | 3) Highlight the device under the Description column and press `CONFIG` or `F4`. 83 | 1) Make sure Enable is set to `FALSE` to modify values 84 | 3) ![Fanuc_3_EIP_Scanner_Setup](Images/Fanuc_3_EIP_Scanner_Setup.png) 85 | 1) For `Name/IP address`, enter the IP address of the CoDeSys controller 86 | 1) For `Vendor Id`, enter `1285` (optional, could leave as 0) 87 | 2) For `Device Type`, enter `12` (optional, could leave as 0) 88 | 3) For `Product Code`, enter `120` (optional, could leave as 0) 89 | 4) Change `Input size (words)` to 2, and repeat the same for `Output size (words)`. Example is to control 32 bits (4 bytes / 2 words), but you can adjust to fit your application 90 | 5) Adjust RPI if needed 91 | 6) Change `Assembly instance (input)` to 101 92 | 7) Change `Assembly instance (output)` to 100 93 | 8) Change `Configuration instance)` to 102 (optional, could leave as 0) 94 | 9) Press `PREV` or `F3` 95 | 4) ![Fanuc_4_EIP_Scanner_Connection](Images/Fanuc_4_EIP_Scanner_Connection.png) 96 | 1) Change Enable from `FALSE` to `TRUE` 97 | 5) ![Fanuc_5_EIP_DIO](Images/Fanuc_5_EIP_DIO.png) 98 | 1) On the teach pendant, press `Menu` 99 | 2) Use arrow key and navigate to `5 I/O` -> `3 Digital`. Example controls bits, but you can change to `5 Group`. 100 | 6) ![Fanuc_6_EIP_DO](Images/Fanuc_6_EIP_DO.png) 101 | 1) Start with `I/O Digital Out`. If you do not see this screen, press `IN/OUT` or `F3` to switch screen 102 | 1) Press `CONFIG` or F2 103 | 2) Change range, example uses DO[1- 32]. 104 | 3) Set `RACK` to 89, which is EtherNet/IP 105 | 4) Set SLOT, example uses slot 2. 106 | 5) Set START, example uses full range so start index is 1 107 | 6) Press `IN/OUT` for `F3` to switch to `I/O Digital In` and repeat the steps from above 108 | 7) Power cycle the controller to apply settings 109 | 110 | #### CoDeSys 111 | 1) ![CoDeSys_1_EIP](Images/CoDeSys_1_EIP.png) 112 | 1) Right click on your controller device (e.g. CODESYS Control for Raspberry Pi MC SL) 113 | 1) Select `Add device` 114 | 2) Expand `Ethernet Adapter` and select `Ethernet` 115 | 3) Click `Add Device` 116 | 2) Double click the newly added `Ethernet (EtherNet)` device 117 | 1) In the `General` tab, press `...` to select the controller's Ethernet network interface 118 | 2) ![CoDeSys_2_EIP_Adapter_General](Images/CoDeSys_2_EIP_Adapter_General.png) 119 | 1) Right click the newly added `Ethernet (Ethernet)` device 120 | 1) Select `Add device` 121 | 2) Expand `EtherNet/IP` -> `EtherNet/IP Local Adapter` and select `EtherNet/IP Adapter` 122 | 3) Click `Add Device` 123 | 3) ![CoDeSys_3_EIP_Adapter_Input](Images/CoDeSys_3_EIP_Adapter_Input.png) 124 | 1) Right click the newly added `Ethernet_IP_Adapter (EtherNet/IP Adapter)` device 125 | 1) Select `Add device` 126 | 2) Select `EtherNet/IP Module` 127 | 3) Click `Add Device` 128 | 2) Double click on the newly added `EtherNet_IP_Module (EtherNet/IP Module)` device 129 | 1) In the `General` tab, select the Module type. Example sends 4 bytes, so select `DWord Input Module` 130 | 4) ![CoDeSys_3_EIP_Adapter_Output](Images/CoDeSys_3_EIP_Adapter_Output.png) 131 | 1) Repeat steps 3 for `DWord Output Module` -------------------------------------------------------------------------------- /Examples/Fanuc/Literature/EtherNetIP_Setup_and_Operations.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Fanuc/Literature/EtherNetIP_Setup_and_Operations.pdf -------------------------------------------------------------------------------- /Examples/Fanuc/README.md: -------------------------------------------------------------------------------- 1 | # CoDeSys_EIP - Fanuc 2 | If you need to handle more than just discrete inputs/outputs, CoDeSys_EIP allows you to read/write robot variables such as numeric registers, position registers, and string registers. 3 | 4 | ### Getting started 5 | Create an function block instance in your CoDeSys program, and specify the robot's IP and port. Then create some variables: 6 | ``` 7 | VAR 8 | _Robot : CoDeSys_EIP.Device(sIpAddress:='192.168.1.220', uiPort:=44818); 9 | _uiIndex : UINT; // state machine 10 | _bReadFinished : BOOL; // read status state 11 | _bWriteFinished : BOOL; // write status state 12 | _bGoodRead : BOOL; // status of read tag 13 | _bGoodWrite : BOOL; // status of write tag 14 | 15 | _stCipService : CoDeSys_EIP.stCipService; 16 | _stLPOS : stCartesianPosition; // current linear position 17 | _stJPOS : stJointPosition; // current joint position 18 | _stPosReg1 : stCartesianPosition; // position register 19 | _stPosReg2 : stJointPosition; // position register 20 | 21 | _rNumReg : REAL; // test numeric register 22 | _diNumReg : DINT; // test numeric register 23 | _stStringRegister : stStringRegister; // test string register 24 | 25 | _stAllRegsInt : stAllRegistersINT; // contains 124 registers 26 | _stBlockRegsInt : stBlockRegistersINT; // contains 10 registers 27 | END_VAR 28 | ``` 29 | 30 | In your code, toggle `bEnable` of _Robot to `TRUE`. There is optional `bAutoReconnect` to re-establish session if terminated from idling (looks like robot controller never closes session). You need to specify Unconnected Messaging (Send RR Data) only via `_Robot.bUnconnectedMessaging := TRUE` (see `Fanuc_RPi.project`)! 31 | 32 | #### Data alignment: 33 | Fanuc robot follows similar layout like Rockwell with 4 bytes alignment when we have gaps (not 4B divisible), so make sure you specify CoDeSys STRUCTs with `{attribute 'pack_mode' := '4'}` when appropriate. Read the [CoDeSys pack mode](https://help.codesys.com/webapp/_cds_pragma_attribute_pack_mode;product=codesys;version=3.5.16.0). 34 | 35 | #### Available services 36 | Below is a table that describes possible operations 37 | 38 | ``` 39 | ===================== 40 | Service Code: 41 | ===================== 42 | * 0x01 (1) : get attribute all 43 | * 0x02 (2) : set attribute all 44 | * 0x0E (14) : get attribute single 45 | * 0x10 (16) : set attribute single 46 | * 0x32 (50) : get attribute block 47 | * 0x33 (51) : set attribute block 48 | ===================== 49 | Class Code: 50 | ===================== 51 | * 0x6B (107) : numeric register (integer) 52 | * 0x6C (108) : numeric register (real) 53 | * 0x6D (109) : string register 54 | * 0x7B (123) : position register (Cartesian) 55 | * 0x7C (124) : position register (joint) 56 | * 0x7D (125) : current position (Cartesian) 57 | * 0x7E (126) : current position (joint) 58 | ===================== 59 | Instance: 60 | ===================== 61 | * 0x01 (1) : single item 62 | * 0x(len)(grp) : block items (e.g. 0x0501 (1281) is for writing 5 items of group 1) 63 | ===================== 64 | Attribute: 65 | ===================== 66 | * 0x01 (1) : index of single item or starting index of block items 67 | ``` 68 | 69 | #### Reading Values 70 | **NOTE**: 71 | * Possible arguments `bGetAttributeAll`, `bGetAttributeSingle`: 72 | * `stSTRUCT` (STRUCT) [**required**]: Struct element. 73 | * `pbBuffer` (POINTER TO BYTE): Pointer to output buffer. 74 | * `uiSize` (UINT): Size of output buffer. 75 | * `psId` (POINTER TO STRING): Pointer to caller id [e.g. psId:=ADR('Read#1: ')]. 76 | * Useful for troubleshooting; output to _PLC.sError. 77 | * `bUnconnected` (BOOL): Forces unconnected messaging (Send RR Data) if `TRUE`. 78 | * Possible arguments `bGenericService`: **Used for get attribute block** 79 | * `stSTRUCT` (STRUCT) [**required**]: Struct element. 80 | * `pbOutBuffer` (POINTER TO BYTE): Pointer to output buffer. 81 | * `uiOutSize` (UINT): Size of output buffer. 82 | * `psId` (POINTER TO STRING): Pointer to caller id [e.g. psId:=ADR('Read#1: ')]. 83 | * Useful for troubleshooting; output to _PLC.sError. 84 | * `bUnconnected` (BOOL): Forces unconnected messaging (Send RR Data) if `TRUE`. 85 | * Function returns TRUE on successful read. 86 | * Numeric value will be casted based on your request. 87 | * **Example:** Numeric register #5 has value of `11.111`. If you request integer (class := 16#6B), then return value will be `11`. If you request real (class := 16#6C), then return value will be `11.111`. 88 | * String register holds up to 253 characters. 89 | * **Position registers**: 90 | * UserFrame has value of 0x3F (63) and ToolFrame of 0x1F (31). 91 | * Reading: 92 | * If you read an uninitialized register, X/Y/Z/W/P/R/EXT1/EXT2/EXT3 (Cartesian) or J1/J2/J3/J4/J5/J6/J7/J8/J9 (joint) will return not a number (NaN). 93 | 94 | Below reads numeric register index 5, which is a **DINT** and writes to a CoDeSys **DINT** called `_diNumReg` 95 | ``` 96 | _stCipService.class := 16#6B; // numeric register (integer) 97 | _stCipService.instance := 16#01; // single item 98 | _stCipService.attribute := 16#05; // register index 5 99 | _bGoodRead := _Robot.bGetAttributeSingle(stCipService:=_stCipService, 100 | pbBuffer:=ADR(_diNumReg), 101 | uiSize:=SIZEOF(_diNumReg), 102 | psId:=ADR('Read R#5: ')); 103 | ``` 104 | Below reads numeric register index 10, which is a **REAL** and writes to a CoDeSys **REAL** called `_rNumReg` 105 | ``` 106 | _stCipService.class := 16#6C; // numeric register (real) 107 | _stCipService.instance := 16#01; // single item 108 | _stCipService.attribute := 16#0A; // register index 10 109 | _bGoodRead := _Robot.bGetAttributeSingle(stCipService:=_stCipService, 110 | pbBuffer:=ADR(_rNumReg), 111 | uiSize:=SIZEOF(_rNumReg), 112 | psId:=ADR('Read R#10: ')); 113 | ``` 114 | Below reads string register index 5, which is a **STRUCT** and writes to a CoDeSys **STRUCT** called `_stStringRegister` 115 | ``` 116 | _stCipService.class := 16#6D; // string register 117 | _stCipService.instance := 16#01; // single item 118 | _stCipService.attribute := 16#05; // register index 5 119 | _bGoodRead := _Robot.bGetAttributeSingle(stCipService:=_stCipService, 120 | pbBuffer:=ADR(_stStringRegister), 121 | uiSize:=SIZEOF(_stStringRegister), 122 | psId:=ADR('Read SR#5: ')); 123 | ``` 124 | Below reads position register index 1, which is a **CARTESIAN** position and writes to a CoDeSys **STRUCT** called `_stPosReg1` 125 | ``` 126 | _stCipService.class := 16#7B; // position register (Cartesian) 127 | _stCipService.instance := 16#01; // single item 128 | _stCipService.attribute := 16#01; // register index 1 129 | _bGoodRead := _Robot.bGetAttributeSingle(stCipService:=_stCipService, 130 | pbBuffer:=ADR(_stPosReg1), 131 | uiSize:=SIZEOF(_stPosReg1), 132 | psId:=ADR('Read PR#1: ')); 133 | ``` 134 | Below reads position register index 2, which is a **JOINT** position and writes to a CoDeSys **STRUCT** called `_stPosReg2` 135 | ``` 136 | _stCipService.class := 16#7C; // position register (joint) 137 | _stCipService.instance := 16#01; // single item 138 | _stCipService.attribute := 16#02; // register index 2 139 | _bGoodRead := _Robot.bGetAttributeSingle(stCipService:=_stCipService, 140 | pbBuffer:=ADR(_stPosReg2), 141 | uiSize:=SIZEOF(_stPosReg2), 142 | psId:=ADR('Read PR#2: ')); 143 | ``` 144 | Below reads the robot's position in **CARTESIAN** format and writes to a CoDeSys **STRUCT** called `_stLPOS` 145 | ``` 146 | _stCipService.class := 16#7D; // current position (Cartesian) 147 | _stCipService.instance := 16#01; // single item 148 | _stCipService.attribute := 16#01; // must be 1 149 | _bGoodRead := _Robot.bGetAttributeSingle(stCipService:=_stCipService, 150 | pbBuffer:=ADR(_stLPOS), 151 | uiSize:=SIZEOF(_stLPOS), 152 | psId:=ADR('Read LPOS: ')); 153 | ``` 154 | Below reads the robot's position in **JOINT** format and writes to a CoDeSys **STRUCT** called `_stJPOS` 155 | ``` 156 | _stCipService.class := 16#7E; // current position (joint) 157 | _stCipService.instance := 16#01; // single item 158 | _stCipService.attribute := 16#01; // must be 1 159 | _bGoodRead := _Robot.bGetAttributeSingle(stCipService:=_stCipService, 160 | pbBuffer:=ADR(_stJPOS), 161 | uiSize:=SIZEOF(_stJPOS), 162 | psId:=ADR('Read JPOS: ')); 163 | ``` 164 | Below reads a block of 10 registers, starting index of 1, as integer and writes to a CoDeSys **STRUCT** called `_stBlockRegsInt` 165 | ``` 166 | _stCipService.cipService := 16#32; // get attribute block 167 | _stCipService.class := 16#6B; // numeric register (integer) 168 | _stCipService.instance := 16#0A01; // 10 (16#0A) elements of group 1 (16#01) 169 | _stCipService.attribute := 16#01; // starting index 1 170 | _bGoodRead := _Robot.bGenericService(stCipService:=_stCipService, 171 | pbOutBuffer:=ADR(_stBlockRegsInt), 172 | uiOutSize:=SIZEOF(_stBlockRegsInt), 173 | psId:=ADR('Read 10 reg: ')); 174 | ``` 175 | 176 | #### Writing Values 177 | **NOTE**: 178 | * Possible arguments `bSetAttributeAll`, `bSetAttributeSingle`: 179 | * `stAttribute` (STRUCT) [**required**]: Struct element. 180 | * `pbBuffer` (POINTER TO BYTE): Pointer to input buffer. 181 | * `uiSize` (UINT): Size of input buffer. 182 | * `psId` (POINTER TO STRING): Pointer to caller id [e.g. psId:=ADR('Write#1: ')]. 183 | * Useful for troubleshooting; output to _Robot.sError. 184 | * `bUnconnected` (BOOL): Forces unconnected messaging (Send RR Data) if `TRUE`. 185 | * Possible arguments `bGenericService`: **Used for set attribute block** 186 | * `stSTRUCT` (STRUCT) [**required**]: Struct element. 187 | * `pbInBuffer` (POINTER TO BYTE): Pointer to input buffer. 188 | * `uiInSize` (UINT): Size of input buffer. 189 | * `psId` (POINTER TO STRING): Pointer to caller id [e.g. psId:=ADR('Write#1: ')]. 190 | * Useful for troubleshooting; output to _PLC.sError. 191 | * `bUnconnected` (BOOL): Forces unconnected messaging (Send RR Data) if `TRUE`. 192 | * Function returns TRUE on successful write 193 | * String register holds up to 253 characters. 194 | * **Position registers**: 195 | * UserFrame has value of 0x3F (63) and ToolFrame of 0x1F (31). 196 | * Writing: 197 | * You can leave the extended axis values unmodified if your application does not use it. 198 | * If you are writing to a position register using Joint mode, do not panic if you do not see correct values. Make sure you have the correct pose representation. 199 | 200 | Below writes the CoDeSys **DINT** called `_diNumReg` to the robot's numeric register index 5 201 | ``` 202 | _stCipService.class := 16#6B; // numeric register (integer) 203 | _stCipService.instance := 16#01; // single item 204 | _stCipService.attribute := 16#05; // register index 5 205 | _bGoodWrite := _Robot.bSetAttributeSingle(stCipService:=_stCipService, 206 | pbBuffer:=ADR(_diNumReg), 207 | uiSize:=SIZEOF(_diNumReg), 208 | psId:=ADR('Write R#5: ')); 209 | ``` 210 | 211 | * 212 | * 213 | * 214 | 215 | Below writes the CoDeSys **STRUCT** called `_stStringRegister` to the robot's string register index 5 216 | **NOTE:** Writing to a robot string register must follow the format of a STRUCT made up of length (DINT) and a STRING. Specify the length before writing! See `Struct` folder for more details 217 | ``` 218 | _stCipService.class := 16#6D; // string register 219 | _stCipService.instance := 16#01; // single item 220 | _stCipService.attribute := 16#05; // register index 5 221 | _bGoodWrite := _Robot.bSetAttributeSingle(stCipService:=_stCipService, 222 | pbBuffer:=ADR(_stStringRegister), 223 | uiSize:=SIZEOF(_stStringRegister), 224 | psId:=ADR('Write SR#5: ')); 225 | ``` 226 | Below writes the CoDeSys **STRUCT** called `_stPosReg2` to the robot's position register index 2 227 | ``` 228 | _stCipService.class := 16#7C; // position register (joint) 229 | _stCipService.instance := 16#01; // single item 230 | _stCipService.attribute := 16#02; // register index 2 231 | _bGoodWrite := _Robot.bSetAttributeSingle(stCipService:=_stCipService, 232 | pbBuffer:=ADR(_stPosReg2), 233 | uiSize:=SIZEOF(_stPosReg2), 234 | psId:=ADR('Write PR#2: ')); 235 | ``` 236 | Below writes the CoDeSys **STRUCT** called `_stBlockRegsInt` as a block of 10 registers to the robot numeric register starting with index 2 237 | ``` 238 | _stCipService.cipService := 16#33; // set attribute block 239 | _stCipService.class := 16#6B; // numeric register (integer) 240 | _stCipService.instance := 16#0A01; // 10 (16#0A) elements of group 1 (16#01) 241 | _stCipService.attribute := 16#02; // register index 2 242 | _bGoodWrite := _Robot.bGenericService(stCipService:=_stCipService, 243 | pbInBuffer:=ADR(_stBlockRegsInt), 244 | uiInSize:=SIZEOF(_stBlockRegsInt), 245 | psId:=ADR('Write 10 reg: ')); 246 | ``` 247 | 248 | ### But does it work? 249 | Testing was done using a Raspberry Pi 3, with CoDeSys 3.5.16.0 runtime installed, to communicate with a Fanuc R-30iB Mate controller running on firmware `V8.30237 (12/7/2017)`. 250 | 251 | **PS**: You will probably find this useful too ztoka -------------------------------------------------------------------------------- /Examples/Fanuc/Struct/stCartesianPosition.txt: -------------------------------------------------------------------------------- 1 | {attribute 'pack_mode' := '0'} 2 | 3 | // Cartesian position struct 4 | TYPE CartPos : 5 | STRUCT 6 | UserFrame : BYTE; 7 | ToolFrame : BYTE; 8 | Reserved : INT; 9 | X : REAL; 10 | Y : REAL; 11 | Z : REAL; 12 | Wx : REAL; 13 | Py : REAL; 14 | Rz : REAL; 15 | TURN4 : SINT; 16 | TURN5 : SINT; 17 | TURN6 : SINT; 18 | //CFG : BYTE; 19 | // configuration 20 | RESERVED1 : BIT; 21 | RESERVED2 : BIT; 22 | RESERVED3 : BIT; 23 | RESERVED4 : BIT; 24 | FRONT : BIT; 25 | UP : BIT; 26 | LEFT : BIT; 27 | FLIP : BIT; 28 | // extended axis 29 | EXT1 : DINT; 30 | EXT2 : DINT; 31 | EXT3 : DINT; 32 | END_STRUCT 33 | END_TYPE 34 | -------------------------------------------------------------------------------- /Examples/Fanuc/Struct/stJointPosition.txt: -------------------------------------------------------------------------------- 1 | {attribute 'pack_mode' := '0'} 2 | 3 | // Joint position struct 4 | TYPE stJointPosition : 5 | STRUCT 6 | UserFrame : BYTE; 7 | ToolFrame : BYTE; 8 | Reserved : INT; 9 | J1 : REAL; 10 | J2 : REAL; 11 | J3 : REAL; 12 | J4 : REAL; 13 | J5 : REAL; 14 | J6 : REAL; 15 | // extended axis 16 | J7 : REAL; 17 | J8 : REAL; 18 | J9 : REAL; 19 | END_STRUCT 20 | END_TYPE 21 | -------------------------------------------------------------------------------- /Examples/Fanuc/Struct/stStringRegister.txt: -------------------------------------------------------------------------------- 1 | {attribute 'pack_mode' := '4'} 2 | 3 | // String struct 4 | TYPE stString : 5 | STRUCT 6 | Length : DINT; 7 | Data : STRING; 8 | END_STRUCT 9 | END_TYPE -------------------------------------------------------------------------------- /Examples/Rockwell/Implicit Messaging/CODESYS EtherNetIP_Configuration_of_Rockwell_Adapter.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Implicit Messaging/CODESYS EtherNetIP_Configuration_of_Rockwell_Adapter.pdf -------------------------------------------------------------------------------- /Examples/Rockwell/Implicit Messaging/Images/CoDeSys_1_EIP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Implicit Messaging/Images/CoDeSys_1_EIP.png -------------------------------------------------------------------------------- /Examples/Rockwell/Implicit Messaging/Images/CoDeSys_2_EIP_Adapter_General.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Implicit Messaging/Images/CoDeSys_2_EIP_Adapter_General.png -------------------------------------------------------------------------------- /Examples/Rockwell/Implicit Messaging/Images/CoDeSys_3_EIP_Adapter_Input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Implicit Messaging/Images/CoDeSys_3_EIP_Adapter_Input.png -------------------------------------------------------------------------------- /Examples/Rockwell/Implicit Messaging/Images/CoDeSys_3_EIP_Adapter_Output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Implicit Messaging/Images/CoDeSys_3_EIP_Adapter_Output.png -------------------------------------------------------------------------------- /Examples/Rockwell/Implicit Messaging/Images/Rockwell_1_EIP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Implicit Messaging/Images/Rockwell_1_EIP.png -------------------------------------------------------------------------------- /Examples/Rockwell/Implicit Messaging/Images/Rockwell_1_EIP_Module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Implicit Messaging/Images/Rockwell_1_EIP_Module.png -------------------------------------------------------------------------------- /Examples/Rockwell/Implicit Messaging/Images/Rockwell_3_EIP_Scanner_General.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Implicit Messaging/Images/Rockwell_3_EIP_Scanner_General.png -------------------------------------------------------------------------------- /Examples/Rockwell/Implicit Messaging/README.md: -------------------------------------------------------------------------------- 1 | # Set up Discrete I/O Communication 2 | Quick tutorial on how to set up I/O between your CoDeSys controller and the Rockwell PLC using implicit messaging. Rockwell forces its PLCs to be in scanner mode (master). 3 | 4 | ### Rockwell Scanner <=> CoDeSys Adapter 5 | 6 | #### Rockwell 7 | 1) ![Rockwell_1_EIP](Images/Rockwell_1_EIP.png) 8 | 1) Right click on the Ethernet bus and click `New Module` 9 | 2) ![Rockwell_1_EIP_Module](Images/Rockwell_1_EIP_Module.png) 10 | 1) On the top right box, type `Generic` to filter the list. 11 | 2) Select `Generic Ethernet Module` 12 | 3) ![Rockwell_3_EIP_Scanner_General](Images/Rockwell_3_EIP_Scanner_General.png) 13 | 1) Double click on the newly added module and fill in the information. 14 | 1) For `Address / Host Name`, enter the IP address of the CoDeSys controller 15 | 2) The input assembly is `101`, the output is `100`, and configuration is `102` 16 | 3) Size column shows how many bytes are to be exchanged. The example uses `32-bit` (4 bytes), but you could always change to `8-bit` or `16-bit`. 17 | 1) Adjust this number up to about 500 bytes total. 18 | 19 | #### CoDeSys 20 | 1) ![CoDeSys_1_EIP](Images/CoDeSys_1_EIP.png) 21 | 1) Right click on your controller device (e.g. CODESYS Control for Raspberry Pi MC SL) 22 | 1) Select `Add device` 23 | 2) Expand `Ethernet Adapter` and select `Ethernet` 24 | 3) Click `Add Device` 25 | 2) Double click the newly added `Ethernet (EtherNet)` device 26 | 1) In the `General` tab, press `...` to select the controller's Ethernet network interface 27 | 2) ![CoDeSys_2_EIP_Adapter_General](Images/CoDeSys_2_EIP_Adapter_General.png) 28 | 1) Right click the newly added `Ethernet (Ethernet)` device 29 | 1) Select `Add device` 30 | 2) Expand `EtherNet/IP` -> `EtherNet/IP Local Adapter` and select `EtherNet/IP Adapter` 31 | 3) Click `Add Device` 32 | 3) ![CoDeSys_3_EIP_Adapter_Input](Images/CoDeSys_3_EIP_Adapter_Input.png) 33 | 1) Right click the newly added `Ethernet_IP_Adapter (EtherNet/IP Adapter)` device 34 | 1) Select `Add device` 35 | 2) Select `EtherNet/IP Module` 36 | 3) Click `Add Device` 37 | 2) Double click on the newly added `EtherNet_IP_Module (EtherNet/IP Module)` device 38 | 1) In the `General` tab, select the Module type. Example sends 4 bytes, so select `DWord Input Module` 39 | 4) ![CoDeSys_3_EIP_Adapter_Output](Images/CoDeSys_3_EIP_Adapter_Output.png) 40 | 1) Repeat steps 3 for `DWord Output Module` -------------------------------------------------------------------------------- /Examples/Rockwell/Literature/CIP.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Literature/CIP.pdf -------------------------------------------------------------------------------- /Examples/Rockwell/Literature/CIP_Vol1_3.3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Literature/CIP_Vol1_3.3.pdf -------------------------------------------------------------------------------- /Examples/Rockwell/Literature/CIP_Vol2_1.4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Literature/CIP_Vol2_1.4.pdf -------------------------------------------------------------------------------- /Examples/Rockwell/Literature/CIP_Vol7_1.7.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Literature/CIP_Vol7_1.7.pdf -------------------------------------------------------------------------------- /Examples/Rockwell/Literature/Controller_CIP.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Literature/Controller_CIP.pdf -------------------------------------------------------------------------------- /Examples/Rockwell/Literature/DataAccess_1756-PM020F-EN-P.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Literature/DataAccess_1756-PM020F-EN-P.pdf -------------------------------------------------------------------------------- /Examples/Rockwell/Literature/DataAccess_1756-RM005A-EN-E.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Literature/DataAccess_1756-RM005A-EN-E.pdf -------------------------------------------------------------------------------- /Examples/Rockwell/Literature/Developer_Guide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Literature/Developer_Guide.pdf -------------------------------------------------------------------------------- /Examples/Rockwell/Literature/GeneralInstructions_1756-RM003_-EN-P.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Literature/GeneralInstructions_1756-RM003_-EN-P.pdf -------------------------------------------------------------------------------- /Examples/Rockwell/Literature/TypeEncode_CIPRW.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Literature/TypeEncode_CIPRW.pdf -------------------------------------------------------------------------------- /Examples/Rockwell/Nonblocking_Loop.project: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Nonblocking_Loop.project -------------------------------------------------------------------------------- /Examples/Rockwell/Read-Write_Tags_RPi.project: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Read-Write_Tags_RPi.project -------------------------------------------------------------------------------- /Examples/Rockwell/Set-Get_Attribute_RPi.project: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Set-Get_Attribute_RPi.project -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/README.md: -------------------------------------------------------------------------------- 1 | # CoDeSys (STRUCT) <-> Rockwell (UDT) 2 | 3 | Reference Rockwell UDTs (*.L5x) 4 | Reference CoDeSys structs (*.txt) 5 | 6 | Rockwell data is aligned on 4/8 bytes, so the CoDeSys structs need to declare bytes packing mode as `{attribute 'pack_mode' := '4'}`. Else, you would need to pad the data with additional bytes. There are some gotchas with string and bits, so take a look at the example structs. I promise there is a pattern...well, kind of. 7 | 8 | For `STRING`, what I have noticed so far is the odd/even length of Rockwell string. For example, a CoDeSys string that can hold 23 max characters (STRING[23]) consumes 24 total bytes since there is a terminatioon character. However, it looks like a Rockwell string SINT[24] doesn't use termination char, but SINT[23] will pad 1 byte to keep it even. Need a Rockwell expert to chime in on this topic. 9 | 10 | For `BIT` alignment of at least 2 bytes is required if the multiple bits can not fill up a byte. Look at `udtBit.L5X` and `stBit.txt` 11 | 12 | If you are unsure about how to convert Rockwell UDT to CoDeSys struct, then the best way is to export the format for inspection. For example, Rockwell STRING24 UDT has 24 bytes. However, if you create a CoDeSys string[24], then you actually use 25 total bytes, and the data types following the string will be incorrectly offset if you organize structs with mixed data types. 13 | 14 | 15 | Rockwell UDT 16 | ``` 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | ``` 31 | 32 | CoDeSys equivalent struct 33 | ``` 34 | {attribute 'pack_mode' := '4'} 35 | 36 | // NOTE: extremely important to have your bytes aligned correctly, 37 | // so read up on pack mode e.g. "{attribute 'pack_mode' := '0'}" 38 | // https://help.codesys.com/webapp/_cds_pragma_attribute_pack_mode;product=codesys;version=3.5.16.0 39 | 40 | // as you can see here, there's a gotcha 41 | // if you set {attribute 'pack_mode' := '0'} 42 | // then you would need the extra padding 43 | TYPE stString24 : 44 | STRUCT 45 | len : UDINT; 46 | text : STRING[23]; 47 | //pad : ARRAY[1..3] OF BYTE; 48 | END_STRUCT 49 | END_TYPE 50 | ``` 51 | 52 | Data is incorrectly offset for `bReady` when you pack into a mixed struct with declaration of string[24]: 53 | ``` 54 | {attribute 'pack_mode' := '4'} 55 | 56 | // NOTE: extremely important to have your bytes aligned correctly, 57 | // so read up on pack mode e.g. "{attribute 'pack_mode' := '0'}" 58 | // https://help.codesys.com/webapp/_cds_pragma_attribute_pack_mode;product=codesys;version=3.5.16.0 59 | 60 | // as you can see here, there's a gotcha 61 | // if you set {attribute 'pack_mode' := '0'} 62 | // then you would need the extra padding 63 | TYPE stAlarmEvent : 64 | STRUCT 65 | sTest1 : stString24; 66 | sTest2 : stString24; 67 | sTest3 : stString24; 68 | sTest4 : stString24; 69 | sMessage : stString; 70 | rValue : REAL; 71 | sTest5 : stString24; 72 | //ZZZZZZZZZZudtAlarmEv7 : SINT; 73 | bReady : BIT; 74 | END_STRUCT 75 | END_TYPE 76 | ``` -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/STRING12.L5X: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/STRING24.L5X: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/STRING25.L5X: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/stBit.txt: -------------------------------------------------------------------------------- 1 | {attribute 'pack_mode' := '4'} 2 | 3 | // NOTE: extremely important to have your bytes aligned correctly, 4 | // so read up on pack mode e.g. "{attribute 'pack_mode' := '0'}" 5 | // https://help.codesys.com/webapp/_cds_pragma_attribute_pack_mode;product=codesys;version=3.5.16.0 6 | 7 | // as you can see here, there's a gotcha 8 | // if you set {attribute 'pack_mode' := '0'} 9 | // then you would need the extra padding 10 | TYPE stBitTest : 11 | STRUCT 12 | bit01 : BIT; 13 | bit02 : BIT; 14 | bit03 : BIT; 15 | //pad : BOOL; 16 | int01 : INT; 17 | bit04 : BIT; // no padding since it's the only bit 18 | sint1 : SINT; 19 | END_STRUCT 20 | END_TYPE -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/stComplex.txt: -------------------------------------------------------------------------------- 1 | {attribute 'pack_mode' := '8'} 2 | 3 | // NOTE: extremely important to have your bytes aligned correctly, 4 | // so read up on pack mode e.g. "{attribute 'pack_mode' := '0'}" 5 | // https://help.codesys.com/webapp/_cds_pragma_attribute_pack_mode;product=codesys;version=3.5.16.0 6 | 7 | // as you can see here, there's a gotcha 8 | // if you set {attribute 'pack_mode' := '0'} 9 | // then you would need the extra padding 10 | TYPE stCodesysMixDatatype : 11 | STRUCT 12 | _udint : UDINT; 13 | _sint : SINT; 14 | //_pad1 : BYTE; // SINT requires padding of byte 15 | _uint : UINT; 16 | _ulint : ULINT; 17 | _dint : DINT; 18 | _string : stString; 19 | //_pad2 : BYTE; 20 | //ZZZZZZZZZZudt_codesy6 : SINT; 21 | _bool : BIT; 22 | //_pad3 : ARRAY [0..1] OF BYTE; // requires 4B alignment, why not 2? 23 | _stMix : stCodesysMixDatatype; 24 | _timer : stTimer; 25 | _arr_real : ARRAY [0..1] OF REAL; 26 | _int : INT; 27 | _aString12 : ARRAY[0..1] OF stString12; 28 | _usint : USINT; 29 | //_pad4 : BYTE; // USINT requires padding of byte 30 | _arr_bool : DWORD; // Rockwell defaults to 32b for array, use DWORD to simplify 31 | _real : REAL; 32 | //_padding : DWORD; // not really sure why we need this 33 | _lreal : LREAL; 34 | _lint : LINT; 35 | END_STRUCT 36 | END_TYPE -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/stFiveStrings.txt: -------------------------------------------------------------------------------- 1 | {attribute 'pack_mode' := '0'} 2 | 3 | // NOTE: extremely important to have your bytes aligned correctly, 4 | // so read up on pack mode e.g. "{attribute 'pack_mode' := '0'}" 5 | // https://help.codesys.com/webapp/_cds_pragma_attribute_pack_mode;product=codesys;version=3.5.16.0 6 | 7 | TYPE stTestCaseFiveStrings : 8 | STRUCT 9 | data : ARRAY[0..4] OF stString25; 10 | END_STRUCT 11 | END_TYPE -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/stMixDatatype.txt: -------------------------------------------------------------------------------- 1 | {attribute 'pack_mode' := '8'} 2 | 3 | // NOTE: extremely important to have your bytes aligned correctly, 4 | // so read up on pack mode e.g. "{attribute 'pack_mode' := '0'}" 5 | // https://help.codesys.com/webapp/_cds_pragma_attribute_pack_mode;product=codesys;version=3.5.16.0 6 | 7 | // as you can see here, there's a gotcha 8 | // if you set {attribute 'pack_mode' := '0'} 9 | // then you would need the extra padding 10 | TYPE stCodesysMixDatatype : 11 | STRUCT 12 | _udint : UDINT; 13 | _sint : SINT; 14 | //_pad1 : BYTE; // SINT requires padding of byte 15 | _uint : UINT; 16 | _ulint : ULINT; 17 | _dint : DINT; 18 | _string : stString; 19 | //_pad2 : BYTE; 20 | //ZZZZZZZZZZudt_codesy6 : SINT; 21 | _bool : BIT; 22 | //_pad3 : ARRAY [0..1] OF BYTE; // requires 4B alignment, why not 2? 23 | _timer : stTimer; 24 | _arr_real : ARRAY [0..1] OF REAL; 25 | _int : INT; 26 | _aString12 : ARRAY[0..1] OF stString12; 27 | _usint : USINT; 28 | //_pad4 : BYTE; // USINT requires padding of byte 29 | _arr_bool : DWORD; // Rockwell defaults to 32b for array, use DWORD to simplify 30 | _real : REAL; 31 | //_padding : DWORD; // looks like 8B alignment is needed 32 | _lreal : LREAL; 33 | _lint : LINT; 34 | END_STRUCT 35 | END_TYPE -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/stMyEvent.txt: -------------------------------------------------------------------------------- 1 | {attribute 'pack_mode' := '4'} 2 | 3 | // NOTE: extremely important to have your bytes aligned correctly, 4 | // so read up on pack mode e.g. "{attribute 'pack_mode' := '0'}" 5 | // https://help.codesys.com/webapp/_cds_pragma_attribute_pack_mode;product=codesys;version=3.5.16.0 6 | 7 | // as you can see here, there's a gotcha 8 | // if you set {attribute 'pack_mode' := '0'} 9 | // then you would need the extra padding 10 | TYPE stAlarmEvent : 11 | STRUCT 12 | sTest1 : stString24; 13 | sTest2 : stString24; 14 | sTest3 : stString24; 15 | sTest4 : stString24; 16 | sMessage : stString; 17 | rValue : REAL; 18 | sTest5 : stString24; 19 | //ZZZZZZZZZZudtAlarmEv7 : SINT; 20 | bReady : BIT; 21 | END_STRUCT 22 | END_TYPE -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/stString.txt: -------------------------------------------------------------------------------- 1 | {attribute 'pack_mode' := '4'} 2 | 3 | // NOTE: extremely important to have your bytes aligned correctly, 4 | // so read up on pack mode e.g. "{attribute 'pack_mode' := '0'}" 5 | // https://help.codesys.com/webapp/_cds_pragma_attribute_pack_mode;product=codesys;version=3.5.16.0 6 | 7 | // as you can see here, there's a gotcha 8 | // if you set {attribute 'pack_mode' := '0'} 9 | // then you would need the extra padding 10 | // Need to specify 82B since CoDeSys STRING defaults to 80 chars + 1 byte for termination 11 | // https://help.codesys.com/webapp/_cds_datatype_string;product=codesys;version=3.5.16.0 12 | TYPE stString : 13 | STRUCT 14 | len : UDINT; 15 | text : STRING[82]; 16 | //pad : ARRAY[0..1] OF BYTE; 17 | END_STRUCT 18 | END_TYPE -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/stString24.txt: -------------------------------------------------------------------------------- 1 | {attribute 'pack_mode' := '4'} 2 | 3 | // NOTE: extremely important to have your bytes aligned correctly, 4 | // so read up on pack mode e.g. "{attribute 'pack_mode' := '0'}" 5 | // https://help.codesys.com/webapp/_cds_pragma_attribute_pack_mode;product=codesys;version=3.5.16.0 6 | 7 | // as you can see here, there's a gotcha 8 | // if you set {attribute 'pack_mode' := '0'} 9 | // then you would need the extra padding 10 | TYPE stString24 : 11 | STRUCT 12 | len : UDINT; 13 | text : STRING[23]; 14 | //pad : BYTE; 15 | END_STRUCT 16 | END_TYPE -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/stString25.txt: -------------------------------------------------------------------------------- 1 | {attribute 'pack_mode' := '4'} 2 | 3 | // NOTE: extremely important to have your bytes aligned correctly, 4 | // so read up on pack mode e.g. "{attribute 'pack_mode' := '0'}" 5 | // https://help.codesys.com/webapp/_cds_pragma_attribute_pack_mode;product=codesys;version=3.5.16.0 6 | 7 | // as you can see here, there's a gotcha 8 | // if you set {attribute 'pack_mode' := '0'} 9 | // then you would need the extra padding 10 | TYPE stString24 : 11 | STRUCT 12 | len : UDINT; 13 | text : STRING[25]; 14 | //pad : ARRAY[1..3] OF BYTE; 15 | END_STRUCT 16 | END_TYPE -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/stTimer.txt: -------------------------------------------------------------------------------- 1 | {attribute 'pack_mode' := '4'} 2 | 3 | // NOTE: extremely important to have your bytes aligned correctly, 4 | // so read up on pack mode e.g. "{attribute 'pack_mode' := '0'}" 5 | // https://help.codesys.com/webapp/_cds_pragma_attribute_pack_mode;product=codesys;version=3.5.16.0 6 | 7 | // It looks like timer must have padding? Need to investigate this 8 | TYPE stTimer : 9 | STRUCT 10 | // EN is 0x80 (128); TT is 0x40 (64); DN is 0x20 (32) 11 | // bitwise: 12 | // EN 10000000 13 | // TT 01000000 14 | // DN 00100000 15 | PAD : ARRAY [1..3] OF BYTE; 16 | PAD1 : BIT; 17 | PAD2 : BIT; 18 | PAD3 : BIT; 19 | PAD4 : BIT; 20 | PAD5 : BIT; 21 | DN : BIT; 22 | TT : BIT; 23 | EN : BIT; 24 | PRE : UDINT; 25 | ACC : UDINT; 26 | END_STRUCT 27 | END_TYPE -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/udtBit.L5X: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/udtComplex.L5X: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/udtFiveStrings.L5X: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/udtMixDatatype.L5X: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/udtMyEvent.L5X: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/udtSTRING.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Struct/udtSTRING.png -------------------------------------------------------------------------------- /Examples/Rockwell/Struct/udtTIMER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NothinRandom/CoDeSys_EIP/993c68dd2f9d88d076ae98eb2a074e2c4b349efa/Examples/Rockwell/Struct/udtTIMER.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 NothinRandom 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CoDeSys_EIP 2 | ### **What?** 3 | 4 | CoDeSys_EIP is a CoDeSys 3.5.16.0 library that allows your CoDeSys controller (IPC) to communicate with various EtherNet/IP (EIP) capable devices such as Allen Bradley / Rockwell programmable logic controller (PLC) through tag based communication or Fanuc robot with EIP set/get attributes; both via explicit messaging. 5 | 6 | ### **Why?** 7 | 8 | In CoDeSys, the current method of communicating with the Rockwell PLC is through implicit messaging. This means you need to set up a generic EIP module on each device and for each task (input/output), where you specify the number of bytes for sending and receiving based on some form of polling (RPI) or triggered / event-based. This is not very flexible as you would need to modify the PLC's code by copying its address data into the EIP module buffer, and then repeat for the IPC... for each PLC that you want to connect to. Additionally, Rockwell PLCs must be the EIP scanner (server), so your device needs to be configured as the EIP adapter (client). Similar for the Fanuc robot, there is no easy way to exchange data from the robot to the Rockwell PLC unless the Enhanced Data Access (EDA) package is purchased. Even then, you still need to use a Rockwell PLC. This library allows your CoDeSys IPC to communicate with the robot controller without additional overhead (see `Examples/Fanuc`). 9 | 10 | CoDeSys_EIP was first inspired by a Python library called [PyLogix](https://github.com/dmroeder/pylogix) and has been improved to handle STRUCTs and generic EIP services. For the control engineers out there, you might already know that writing PLC code is not as flexible as writing higher level languages such as Python/Java/etc, where you can create variables with virtually any data type on the fly; thus, this library was heavily modified to accomodate control requirements. It is written to operate asynchronously (non-blocking) to avoid watchdog alerts, which means you make the call and be notified when data has been read/written succesfully. If you need to read multiple variables in one scan, then you can create a lower priority task and place the calls into a WHILE loop (see `Examples/Rockwell/Read-Write_Tags_RPi.project`). At least 95% of the library leverages pointers for efficiency, so it might not be straight forward to digest at first. The documentation / comments is not too bad, but feel free to raise issues if needed. 11 | 12 | ### **Getting started** 13 | Create an function block instance in your CoDeSys program, and specify the PLC's IP and port. Then create some variables: 14 | ``` 15 | VAR 16 | _PLC : CoDeSys_EIP.Device(sIpAddress:='192.168.1.219', uiPort:=44818); 17 | _bReadTag_codesys_bool : BOOL; 18 | _siReadTag_codesys_sint : SINT; 19 | _usiReadTag_codesys_usint : USINT; 20 | _iReadTag_codesys_int : INT; 21 | _uiReadTag_codesys_uint : UINT; 22 | _diReadTag_codesys_dint : DINT; 23 | _udiReadTag_codesys_udint : UDINT; 24 | _liReadTag_codesys_lint : LINT; 25 | _uliReadTag_codesys_ulint : ULINT; 26 | _rReadTag_codesys_real : REAL; 27 | _lrReadTag_codesys_lreal : LREAL; 28 | _sReadTag_codesys_string : STRING; 29 | _stReadTag_codesys_mixed : stMixDatatype; // contains all data types above in random order 30 | _stReadTag_testCaseFiveStrings : stString25; // custom string size of 25 chars 31 | _stReadTag_testCaseFiveStrings2 : ARRAY [1..2] OF stString25; // two stString25 elements for multi-read/write 32 | 33 | _bWriteTag_codesys_bool_local : BOOL; // program tag 34 | _stWriteTag_codesys_string : stString; // STRUCT with string length (DINT) and string (82 is termination) 35 | END_VAR 36 | ``` 37 | 38 | In your code, toggle `bEnable` of _PLC to `TRUE`. There is optional `bAutoReconnect` to re-establish session if terminated from idling. 39 | 40 | #### **Data alignment:** 41 | Allen Bradley is 4/8 bytes aligned, so make sure you specify CoDeSys STRUCTs with `{attribute 'pack_mode' := '4'}`. Read the [CoDeSys pack mode](https://help.codesys.com/webapp/_cds_pragma_attribute_pack_mode;product=codesys;version=3.5.16.0). 42 | 43 | ### **Reading Tag** 44 | **NOTE**: 45 | * Add "Program:{programName}." prefix to read program tags (e.g. Program:MainProgram.codesys_bool_local) 46 | * Possible arguments for `bRead`: 47 | * `psTag` (POINTER TO STRING) [**required**]: Pointer to the string tag [e.g. psTag:=ADR('codesys_bool')]. If string is empty, bRead throws error and returns `FALSE`. 48 | * You can also point to a STRING instead [e.g. psTag:=ADR(_sMyTestString)]. 49 | * `eDataType` (ENUM) [**required**]: Expected CIP data type. If read response does not match, a read error is raised (avoids buffer overflow). 50 | * `pbBuffer` (POINTER TO BYTE) [**required**]: Pointer to the output buffer. 51 | * `uiSize` (UINT): Size of the output buffer (pbBuffer). 52 | * `uiElements` (UINT): Elements to be requested; used when specifying array. 53 | * Default: `1` 54 | * `psId` (POINTER TO STRING): Pointer to caller id [e.g. psId:=ADR('Read#1: ')]. 55 | * Useful for troubleshooting. If you incorrectly declare your tag `codesys_boo` instead of `codesys_bool`, a `Path Segment Error` would typically be returned. By declaring `psId:=ADR('Read#1: ')`, sError will return `'Read#1: Path Segment Error'` to let you know of the CIP error. 56 | * `bUnconnected` (BOOL): Forces unconnected messaging (Send RR Data) if `TRUE`. 57 | * `bRead` returns TRUE on successful read. 58 | 59 | Below reads the PLC controller tag `codesys_bool` with data type of **BOOL** and writes to a CoDeSys **BOOL** called `_bReadTag_codesys_bool`. 60 | ``` 61 | _PLC.bRead(psTag:=ADR('codesys_bool'), 62 | eDataType:=CoDeSys_EIP.eCipTypes._BOOL, 63 | pbBuffer:=ADR(_bReadTag_codesys_bool), 64 | psId:=ADR('Read#1: ')); 65 | ``` 66 | 67 | * 68 | * 69 | * 70 | 71 | Below reads the PLC controller tag `codesys_dint` with data type of **DINT** and writes to a CoDeSys **DINT** called `_diReadTag_codesys_dint`. 72 | ``` 73 | _PLC.bRead(psTag:=ADR('codesys_dint'), 74 | eDataType:=CoDeSys_EIP.eCipTypes._DINT, 75 | pbBuffer:=ADR(_diReadTag_codesys_dint)); 76 | ``` 77 | 78 | * 79 | * 80 | * 81 | 82 | Below reads the PLC controller tag `codesys_lreal` with data type of **LREAL** and writes to a CoDeSys **LREAL** called `_lrReadTag_codesys_lreal`. 83 | ``` 84 | _PLC.bRead(psTag:=ADR('codesys_lreal'), 85 | eDataType:=CoDeSys_EIP.eCipTypes._LREAL, 86 | pbBuffer:=ADR(_lrReadTag_codesys_lreal)); 87 | ``` 88 | Below reads the PLC controller tag `codesys_string` with data type of **STRUCT** and writes to a CoDeSys **STRING** called `_sReadTag_codesys_string`. 89 | **Note**: If you do not specify `uiSize`, then the output will only contain the string data and not the string length (DINT). 90 | ``` 91 | _PLC.bRead(psTag:=ADR('codesys_string'), 92 | eDataType:=CoDeSys_EIP.eCipTypes._STRUCT, 93 | pbBuffer:=ADR(_sReadTag_codesys_string)); 94 | ``` 95 | Below reads the PLC controller UDT `codesys_mixed` with data type of a **"complex" STRUCT** and writes to a CoDeSys **STRUCT** called `_stReadTag_codesys_mixed`. 96 | **Note**: Specify the size of the CoDeSys STRUCT. See `Examples/Rockwell` folder for more details. 97 | ``` 98 | _PLC.bRead(psTag:=ADR('codesys_mixed'), 99 | eDataType:=CoDeSys_EIP.eCipTypes._STRUCT, 100 | pbBuffer:=ADR(_stReadTag_codesys_mixed), 101 | uiSize:=SIZEOF(_stReadTag_codesys_mixed)); 102 | ``` 103 | Below reads the PLC tag `codesys_string_local` of a program called `MainProgram` with data type of **STRUCT** and writes to a CoDeSys **STRUCT** called `_stReadTag_codesys_string_local`. 104 | **Note**: Specify `uiSize` since STRUCT also has string length (DINT). See `Examples/Rockwell` folder for more details. 105 | ``` 106 | _PLC.bRead(psTag:=ADR('Program:MainProgram.codesys_string_local'), 107 | eDataType:=CoDeSys_EIP.eCipTypes._STRUCT, 108 | pbBuffer:=ADR(_stReadTag_codesys_string_local), 109 | uiSize:=SIZEOF(_stReadTag_codesys_string_local)); 110 | ``` 111 | Below reads the array index 3 of a PLC controller tag `testCaseFiveStrings` with data type of **STRING25** and writes to a CoDeSys **STRUCT** called `_stReadTag_testCaseFiveStrings`. 112 | **Note**: Specify `uiSize` since STRUCT also has string length (DINT). See `Examples/Rockwell` folder for more details. 113 | ``` 114 | _PLC.bRead(psTag:=ADR('testCaseFiveStrings.strTest[3]'), 115 | eDataType:=CoDeSys_EIP.eCipTypes._STRUCT, 116 | pbBuffer:=ADR(_stReadTag_testCaseFiveStrings), 117 | uiSize:=SIZEOF(_stReadTag_testCaseFiveStrings)); 118 | ``` 119 | Below reads 2 elements (starting index of 1) from PLC controller tag `testCaseFiveStrings` with data type of **STRING25** and writes to a CoDeSys **STRUCT** called `_stReadTag_testCaseFiveStrings2`. 120 | **Note**: Specify `uiSize` since STRUCT also has string length (DINT). See `Examples/Rockwell` folder for more details. 121 | ``` 122 | _PLC.bRead(psTag:=ADR('testCaseFiveStrings.strTest[1]'), 123 | eDataType:=CoDeSys_EIP.eCipTypes._STRUCT, 124 | pbBuffer:=ADR(_stReadTag_testCaseFiveStrings2), 125 | uiSize:=SIZEOF(_stReadTag_testCaseFiveStrings2), 126 | uiElements:=2); 127 | ``` 128 | 129 | ### **Writing Tag** 130 | **NOTE**: 131 | * Add "Program:{programName}." prefix to write program tags (e.g. Program:MainProgram.codesys_bool_local) 132 | * If you are writting to a tag that has not been read in yet, then an extra read request is performed first, and the STRUCT identifier of the response data is captured in `_stKnownStructs` "dictionary". All subsequent writes of the same tag will perform a dictionary look up to save time. 133 | * Possible arguments for `bWrite`: 134 | * `psTag` (POINTER TO STRING) [**required**]: Pointer to the string tag [e.g. psTag:=ADR('codesys_bool')]. If string is empty, bWrite throws error and returns `FALSE`. 135 | * You can also point to a STRING instead [e.g. psTag:=ADR(_sMyTestString)]. 136 | * `eDataType` (ENUM) [**required**]: Defined CIP data type. If write response does not match, a write error is raised. 137 | * `pbBuffer` (POINTER TO BYTE) [**required**]: Pointer to the input buffer. 138 | * `uiSize` (UINT): Size of the input buffer (pbBuffer). 139 | * `uiElements` (UINT): Elements to be requested; used when specifying array. 140 | * Default: `1` 141 | * `psId` (POINTER TO STRING): Pointer to caller id [e.g. psId:=ADR('Write#1: ')]. 142 | * Useful for troubleshooting; output to _PLC.sError. 143 | * `bUnconnected` (BOOL): Forces unconnected messaging (Send RR Data) if `TRUE`. 144 | * `bWrite` returns TRUE on successful write 145 | 146 | Below writes the CoDeSys **BOOL** called `_bWriteTag_codesys_bool_local` to the PLC tag `codesys_bool_local` of a program called `MainProgram` 147 | ``` 148 | _PLC.bWrite(psTag:=ADR('Program:MainProgram.codesys_bool_local'), 149 | eDataType:=CoDeSys_EIP.eCipTypes._BOOL, 150 | pbBuffer:=ADR(_bWriteTag_codesys_bool_local), 151 | psId:=ADR('Write#19: ')); 152 | ``` 153 | * 154 | * 155 | * 156 | 157 | Below writes the CoDeSys **LREAL** called `_lrWriteTag_codesys_lreal_local` to the PLC tag `codesys_lreal_local` of a program called `MainProgram` 158 | ``` 159 | _PLC.bWrite(psTag:=ADR('Program:MainProgram.codesys_lreal_local'), 160 | eDataType:=CoDeSys_EIP.eCipTypes._LREAL, 161 | pbBuffer:=ADR(_lrWriteTag_codesys_lreal_local)); 162 | ``` 163 | Below writes the CoDeSys **STRUCT** called `_stWriteTag_codesys_string` to the PLC controller tag `codesys_string`. 164 | **NOTE:** Writing to a PLC string must follow the format of a STRUCT made up of length (DINT) and a STRING. Specify the length before writing! See `Examples/Rockwell` folder for more details 165 | ``` 166 | _PLC.bWrite(psTag:=ADR('codesys_string'), 167 | eDataType:=CoDeSys_EIP.eCipTypes._STRUCT, 168 | pbBuffer:=ADR(_stWriteTag_codesys_string), 169 | uiSize:=SIZEOF(_stWriteTag_codesys_string)); 170 | ``` 171 | 172 | ### **Set/Get Attributes** 173 | **NOTE**: 174 | * You will need to create a STRUCT and specify what you are getting/setting 175 | * You could share the same STRUCT if space is a concern (i.e. use `_stCipService : CoDeSys_EIP.stCipService` for both set/get) 176 | * Possible arguments for `bGetAttributeAll`, `bSetAttributeAll`, `bGetAttributeSingle`, `bSetAttributeSingle`, `bGetAttributeList`, `bSetAttributeList`: 177 | * `stSTRUCT` (STRUCT) [**required**]: Struct element. 178 | * `pbBuffer` (POINTER TO BYTE) [**required**]: Pointer to either input/output buffer based on type set/get. 179 | * `uiSize` (UINT) [**required**]: Size of input/output buffer (pbBuffer). 180 | * `psId` (POINTER TO STRING): Pointer to caller id [e.g. psId:=ADR('GetPlcTime: ')]. 181 | * Useful for troubleshooting; output to _PLC.sError. 182 | * `bUnconnected` (BOOL): Forces unconnected messaging (Send RR Data) if `TRUE`. 183 | * Possible arguments for `bGenericService`: **typically populate either pbInBuffer/uiInSize or pbOutBuffer/uiOutSize** 184 | * `stSTRUCT` (STRUCT) [**required**]: Struct element. 185 | * `pbInBuffer` (POINTER TO BYTE): Pointer to input buffer. 186 | * `uiInSize` (UINT): Size of input buffer. 187 | * `pbOutBuffer` (POINTER TO BYTE): Pointer to output buffer. 188 | * `uiOutSize` (UINT): Size of output buffer. 189 | * `psId` (POINTER TO STRING): Pointer to caller id [e.g. psId:=ADR('ExecPCCC: ')]. 190 | * Useful for troubleshooting; output to _PLC.sError. 191 | * `bUnconnected` (BOOL): Forces unconnected messaging (Send RR Data) if `TRUE`. 192 | * Function returns TRUE on successful read/write 193 | * See `Examples/Rockwell/Set-Get_Attribute_RPi.project` for more details 194 | * **Examples:** 195 | * Create a re-usable STRUCT or one for each service type: `_stCipService : CoDeSys_EIP.stCipService;` 196 | * Get PLC audit value (checksum) 197 | * Create a LWORD/ULINT for the result: `_lwAuditValue : LWORD;` 198 | * Specify parameters: 199 | * _stCipService.class := 16#8E; // 142 200 | * _stCipService.instance := 16#01; // 1 201 | * _stCipService.attribute := 16#1B; // 27 202 | * Call function `bGetAttributeSingle(stCipService:=_stCipService, pbBuffer:=ADR(_lwAuditValue), uiSize:=SIZEOF(_lwAuditValue), psId:=ADR('AuditValue: '))` 203 | * If there is an error, message will have header of `AuditValue` 204 | * Set PLC Change To Detect Mask 205 | * Create a LWORD/ULINT for the value: `_lwMask : LWORD;` 206 | * Specify parameters: 207 | * _stCipService.class := 16#8E; // 142 208 | * _stCipService.instance := 16#01; // 1 209 | * _stCipService.attribute := 16#1C; // 28 210 | * Call function `bSetAttributeSingle(stCipService:=_stCipService, pbBuffer:=ADR(_lwMask), uiSize:=SIZEOF(_lwMask), psId:=ADR('Mask: '))` 211 | * If there is an error, message will have header of `Mask` 212 | * Get the PLC time (look at `bGetPlcTime()` to see how it is implemented) 213 | * Create a STRUCT to store result: `_stPlcTime : stPlcTime;` 214 | * Specify parameters: 215 | * _stCipService.class := 16#8B; // 139 216 | * _stCipService.instance := 16#01; // 1 217 | * _stCipService.attributeCount := 16#01; // 1 218 | * _stCipService.attributeList[1] := 16#0B; // we are only getting one attribute here 219 | * Call function `bGetAttributeList(stCipService:=_stCipService, pbBuffer:=ADR(_stPlcTime), uiSize:=SIZEOF(_stPlcTime), psId:=ADR('GetPlcTime: '))` 220 | * Set the PLC time (look at `bSetPlcTime(ULINT)` to see how it is implemented) 221 | * Create a ULINT that stores time in microseconds: `_uliSetTime : ULINT;` 222 | * Specify parameters: 223 | * _stCipService.class := 16#8B; // 139 224 | * _stCipService.instance := 16#01; // 1 225 | * _stCipService.attributeCount := 16#01; // 1 226 | * _stCipService.attributeList[1] := 16#06; // 6 227 | * Call function `bSetAttributeList(stCipService:=_stCipService, pbBuffer:=ADR(_uliSetTime), uiSize:=SIZEOF(_uliSetTime), psId:=ADR('SetPlcTime: '))` 228 | 229 | ### **But does it work?** 230 | Yes... 60% of the time, it works every time. Testing was done using a Raspberry Pi 3, with CoDeSys 3.5.16.0 runtime installed, to communicate with a Rockwell `5069-L330ERMS2/A` safety PLC. Each read/write instruction averaged approximately 3.7 milliseconds using WiFi and around 900 microseconds over wired in test project (see `Examples/Rockwell/Read-Write_Tags_RPi.project`), but your mileage might vary. You will need to install SysTime and OSCAT Basic (this is for time formatting). 231 | #### **Tested controllers:** 232 | * `1769-L33ERMS/A` 233 | * `1769-L36ERMS/A` 234 | * `5069-L330ERMS2/A` 235 | * `1756-L81E/B` 236 | 237 | ### **Current features** 238 | 239 | #### **List Identity** 240 | `bGetListIdentity()` (BOOL) is automatically called after TCP connection is established to return device info. You could scan your network for other EtherNet/IP capable devices. 241 | * **Examples:** 242 | * Retrieve single parameter as UINT using: `_uiVendorId := _PLC.uiVendorId;` 243 | * **Output:** `1` 244 | * Retrieve single parameter as STRING using: `_sVendorId := _PLC.sVendorId;` 245 | * **Output:** `'Rockwell Automation/Allen-Bradley'` 246 | * Retrieve entire STRUCT using: `_stDevice := _PLC.stListIdentity;` 247 | * Requires STRUCT variable: `_stDevice : CoDeSys_EIP.stListIdentity;` 248 | * **Output:** 249 | * encapsulationVersion: `1` 250 | * socketFamily: `2` 251 | * socketPort: `44818` 252 | * socketAddress: `'192.168.1.219'` 253 | * socketZero: `0` 254 | * vendorId: `'Rockwell Automation/Allen-Bradley'` 255 | * deviceType: `'Programmable Logic Controller'` 256 | * productCode: `223` 257 | * revision: `'32.11'` 258 | * status: `'0x3060'` 259 | * serialNumber: `'0x60D789F4'` 260 | * productName: `'5069-L330ERMS2/A'` 261 | * state: `3` 262 | * To make status (e.g. `'0x3060'`) more meaningful: 263 | * Requires STRUCT variable: `_stDeviceStatus := CoDeSys_EIP.stListIdentityStatus;` 264 | * Retrieve using `_stDeviceStatus := _PLC.stDeviceStatus;` 265 | * **Output:** 266 | * owned: `FALSE` 267 | * configured: `FALSE` 268 | * extendedDeviceStatus: `'At least one I/O connection in run mode'` 269 | * faulted: `TRUE` 270 | * minorRecoverableFault: `TRUE` 271 | * minorUnrecoverableFault: `FALSE` 272 | * majorRecoverableFault: `FALSE` 273 | * majorUnrecoverableFault: `FALSE` 274 | * To make state (e.g. `3`) more meaningful, use sDeviceState: 275 | 276 | #### **Get/Set PLC time** 277 | `bGetPlcTime()` (BOOL) requests the current PLC time. The function can handle 64b time up to nanoseconds resolution, but the PLC's accuracy is only available at the microseconds. 278 | * **Example:** 279 | * Retrieve time as STRING: `_sPlcTime := _PLC.sPlcTime;` 280 | * **Output:** `'LDT#2020-07-10-01:05:59.409036000'` 281 | * Retrieve time in microseconds as ULINT: `_uliPlcTime := _PLC.uliPlcTime;` 282 | * **Output:** `1593815478238754` 283 | 284 | `bSetPlcTime(ULINT)` (BOOL) sets the PLC time. 285 | * **Examples:** 286 | * Synchronize PLC's time to IPC's time: `bSetPlcTime()` 287 | * Set a PLC time to `Friday, July 3, 2020 10:31:18 PM GMT` with seconds level accuracy in microseconds: `bSetPlcTime(1593815478000000)` 288 | * **NOTE:** Look at built-in `Timestamp` function block 289 | 290 | #### **Detect Code Changes** 291 | From a security perspective, it is useful to detect changes on the Rockwell PLC. 292 | `bGetPlcAuditValue()` (BOOL) requests the PLC audit value. 293 | * **Example:** 294 | * Retrieve audit value as ULINT: `_uliAuditValue := _PLC.uliAuditValue;` 295 | * **Output:** `12650121977826373092` (16#AF8E42EA65B3EDE4) 296 | * Retrieve audit value as STRING: `_sAuditValue := _PLC.sAuditValue;` 297 | * **Output:** `'0xAF8E42EA65B3EDE4'` 298 | 299 | `bGetPlcMask()` (BOOL) requests the PLC Change To Detect mask. 300 | * **Example:** 301 | * Retrieve mask value as ULINT: `_uliMask := _PLC.uliMask;` 302 | * **Output:** `18446744073709551615` (16#FFFFFFFFFFFFFFFF) 303 | * Retrieve mask value as STRING: `_sMask := _PLC.sMask;` 304 | * **Output:** `'0xFFFFFFFFFFFFFFFF'` 305 | 306 | `bSetPlcMask(ULINT)` (BOOL) *should* set the PLC Change To Detect mask. 307 | 308 | **NOTE:** currently throwing a `Privilege Violation` error, need to investigate. 309 | * **Examples:** 310 | * Set the mask to 0xFFFF: `bSetPlcMask(65535)` or `bSetPlcMask(16#FFFF)` 311 | 312 | ### **Useful parameters (SET/GET)** 313 | **NOTE:** There are a lot more, so dive into library to see what works best for you 314 | * `bAutoReconnect` (BOOL) re-establishes session if PLC closes it after idling (no read/write request) for roughly 60 seconds. 315 | * Default: `FALSE` 316 | * Example set: `_PLC.bAutoReconnect := TRUE;` 317 | * Example get: `_bReconnect := _PLC.bAutoReconnect;` 318 | * `bHexPrefix` (BOOL) attaches the '0x' prefix to the strings from list identity STRUCT. 319 | * Default: `TRUE` 320 | * `bUnconnectedMessaging` (BOOL) skips forward open and default to use unconnected messaging (Send RR Data). 321 | * Default: `FALSE` 322 | * `bMicro800` (BOOL) specifies the device as a Micro800. 323 | * Default: `FALSE` 324 | * Not tested yet; need a Micro800 PLC. 325 | * `bProcessorSlot` (BYTE) specifies the processor slot. 326 | * Default: `0` 327 | * `sDeviceIp` (STRING) allows you to change device IP from the one specified initially. 328 | * Default: `11.200.0.10` 329 | * Toggle bEnable to update settings 330 | * `uiDevicePort` (UINT) allows you to change device port from the one specified initially. 331 | * Default: `44818` 332 | * Toggle bEnable to update settings 333 | * `udiTcpWriteTimeout` (UDINT) specifies the maximum time it should take for the TCP client write to finish. 334 | * Default: `200000` microseconds 335 | * `uiCipRequestTimeout` (UINT) specifies the maximum time it should take for a CIP request to finish. 336 | * Default: `200` milliseconds 337 | * `udiTcpClientTimeOut` (UDINT) specifies the TCP client timeout. 338 | * Default: `500000` microseconds 339 | * `udiTcpClientRetry` (UDINT) specifies the auto reconnect interval for `bAutoReconnect`. 340 | * Default: `5000` milliseconds 341 | * `uiConnectionSize` (UINT) specifies the maximum connection bytes size for each read/write transaction. Value is automatically resized to gvcParameters.uiTcpBuffer if it exceeds it. 342 | * Default: `508` 343 | * Using 508, which is divisible by 4. 344 | * **NOTE:** Large forward open has value greater than 511, and values of over 4000 seems to return `Resource Unavailable` error on CompactLogix. 345 | * `stRoute` (STRUCT) specifies a non-standard route that cannot be generated by `bBuildRoute(STRING)`. 346 | * **NOTE:** Max route length is 96 bytes, and make sure to specify STRUCT length so forward open knows how to properly build the route. 347 | 348 | ### **Useful variable lists:** 349 | * `gvcParameters` holds the default values from above, so adjust values to fit your needs. 350 | 351 | ### **Useful methods:** 352 | * `bCloseSession()` (BOOL) sends forward close request and then unregister session request. 353 | * **NOTE:** If `bAutoReconnect` is set to TRUE, session will be re-established within the specified `udiTcpClientRetry` period. 354 | * `bResetFault()` (BOOL) call this to ACK read/write fault flags. 355 | * `bBuildRoute(STRING)` (BOOL) if you have a custom route. 356 | * Example: argument of ENBT1 -> ENBT2 -> PLC might look like `'4,192.168.1.219'`. 357 | 358 | **PS**: In memories of ztoka (April 2012 - May 2020). This is for you bud. --------------------------------------------------------------------------------