├── LICENSE ├── README.md ├── eip_bridge ├── CMakeLists.txt ├── example │ └── eip_config.yaml ├── launch │ └── eip_bridge.launch ├── package.xml ├── scripts │ ├── eip_bridge_node.py │ └── eip_bridge_simulator_node.py ├── setup.py └── src │ ├── .gitignore │ └── eip_bridge │ ├── __init__.py │ ├── eip_bridge.py │ ├── eip_functions.py │ ├── eip_publisher.py │ ├── eip_service.py │ ├── eip_subscriber.py │ └── plc_simulator.py └── eip_msgs ├── CMakeLists.txt ├── package.xml └── srv ├── ReadTagArrayFloat32.srv ├── ReadTagArrayInt16.srv ├── ReadTagArrayInt32.srv ├── ReadTagArrayInt64.srv ├── ReadTagArrayInt8.srv ├── ReadTagBool.srv ├── ReadTagFloat32.srv ├── ReadTagInt16.srv ├── ReadTagInt32.srv ├── ReadTagInt64.srv ├── ReadTagInt8.srv ├── ReadTagString.srv ├── SetMode.srv ├── WriteTagArrayFloat32.srv ├── WriteTagArrayInt16.srv ├── WriteTagArrayInt32.srv ├── WriteTagArrayInt64.srv ├── WriteTagArrayInt8.srv ├── WriteTagBool.srv ├── WriteTagFloat32.srv ├── WriteTagInt16.srv ├── WriteTagInt32.srv ├── WriteTagInt64.srv ├── WriteTagInt8.srv └── WriteTagString.srv /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EIP Bridge 2 | [![license - Apache 2.0](https://img.shields.io/:license-Apache%202.0-yellowgreen.svg)](https://opensource.org/licenses/Apache-2.0) 3 | 4 | This repository contains a bridge between a ROS system and PLC using the Ethernet/IP (EIP) communication protocol. 5 | 6 | ## Dependencies: 7 | 1. [PyComm 1.0.8](https://github.com/ruscito/pycomm) 8 | - `pip install pycomm==1.0.8` 9 | 10 | ## Notes: 11 | - Supported message types: 12 | - Core PLC data types (e.g. integers, floats, booleans, strings, etc.) 13 | - Fixed-length arrays of core PLC data types 14 | - Not currently supported: 15 | - User-defined type (UDT) 16 | - User-defined type array 17 | - Currently only tested on Allen Bradley PLCs 18 | 19 | ## Configuration 20 | The EIP bridge accepts a YAML config file specifying communication between the ROS system and PLC. This includes: 21 | - `Subscribers`: PLC tags which should receive data from ROS topics (i.e. ROS -> PLC) 22 | - `Publishers`: PLC tags which should publish their values as ROS messages (i.e. PLC -> ROS) 23 | - `Services`: on-demand access via ROS service for getting/setting data of a specified type on arbitrary tags 24 | 25 | The format of the YAML configuration file is shown below: 26 | 27 | ``` yaml 28 | Services: 29 | - name: 30 | - type: 31 | Publishers: 32 | - name: 33 | tag: 34 | type: 35 | length: 36 | sim_value: 37 | Subscribers: 38 | - name: 39 | tag: 40 | type: 41 | length: 42 | sim_value: 43 | ``` 44 | See the [example configuration file](eip_bridge/example/eip_config.yaml) for reference 45 | 46 | ## Usage: 47 | 1. Create YAML file listing ROS topics to map to EIP tags 48 | 1. **(Optional)** Add the path to `pycomm` to the `PYTHONPATH` environment variable 49 | 50 | ``` bash 51 | export PYTHONPATH= 52 | ``` 53 | > Note: this should not be necessary if `pycomm` was installed using `pip` 54 | 1. Run the launch file, specifying the path to your config file 55 | 56 | ``` bash 57 | roslaunch eip_bridge eip_bridge.launch config_file:='/path/to/config.yaml' sim:= 58 | ``` 59 | -------------------------------------------------------------------------------- /eip_bridge/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(eip_bridge) 3 | 4 | find_package(catkin REQUIRED COMPONENTS 5 | rospy 6 | eip_msgs 7 | ) 8 | 9 | catkin_python_setup() 10 | 11 | catkin_package( 12 | CATKIN_DEPENDS 13 | eip_msgs 14 | rospy 15 | ) 16 | 17 | ############# 18 | ## Install ## 19 | ############# 20 | 21 | catkin_install_python( 22 | PROGRAMS 23 | scripts/eip_bridge_node.py 24 | scripts/eip_bridge_simulator_node.py 25 | DESTINATION 26 | ${CATKIN_PACKAGE_BIN_DESTINATION} 27 | ) 28 | 29 | foreach(dir example launch) 30 | install(DIRECTORY ${dir}/ 31 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/${dir}) 32 | endforeach() 33 | -------------------------------------------------------------------------------- /eip_bridge/example/eip_config.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | # Services Fields: 3 | # - name: ROS Topic to publish to 4 | # - type: ROS service definition 5 | Services: 6 | - name: 'eip_bridge/read_tag_int8' 7 | type: 'ReadTagInt8' 8 | 9 | - name: 'eip_bridge/read_tag_int16' 10 | type: 'ReadTagInt16' 11 | 12 | - name: 'eip_bridge/read_tag_int32' 13 | type: 'ReadTagInt32' 14 | 15 | - name: 'eip_bridge/read_tag_int64' 16 | type: 'ReadTagInt64' 17 | 18 | - name: 'eip_bridge/read_tag_string' 19 | type: 'ReadTagString' 20 | 21 | - name: 'eip_bridge/read_tag_array_int8' 22 | type: 'ReadTagArrayInt8' 23 | 24 | - name: 'eip_bridge/read_tag_array_int16' 25 | type: 'ReadTagArrayInt16' 26 | 27 | - name: 'eip_bridge/read_tag_array_int32' 28 | type: 'ReadTagArrayInt32' 29 | 30 | - name: 'eip_bridge/read_tag_array_int64' 31 | type: 'ReadTagArrayInt64' 32 | 33 | - name: 'eip_bridge/write_tag_int8' 34 | type: 'WriteTagInt8' 35 | 36 | - name: 'eip_bridge/write_tag_int16' 37 | type: 'WriteTagInt16' 38 | 39 | - name: 'eip_bridge/write_tag_int32' 40 | type: 'WriteTagInt32' 41 | 42 | - name: 'eip_bridge/write_tag_int64' 43 | type: 'WriteTagInt64' 44 | 45 | - name: 'eip_bridge/write_tag_string' 46 | type: 'WriteTagString' 47 | 48 | - name: 'eip_bridge/write_tag_array_int8' 49 | type: 'WriteTagArrayInt8' 50 | 51 | - name: 'eip_bridge/write_tag_array_int16' 52 | type: 'WriteTagArrayInt16' 53 | 54 | - name: 'eip_bridge/write_tag_array_int32' 55 | type: 'WriteTagArrayInt32' 56 | 57 | - name: 'eip_bridge/write_tag_array_int64' 58 | type: 'WriteTagArrayInt64' 59 | 60 | # Subscriber Fields: 61 | # - name: ROS Topic to publish to 62 | # - type: ROS std_msgs data type 63 | # - length: array length, or 0 if not an array 64 | # - tag (optional): plc tag name; otherwise plc tag name = topic name without 'eip_bridge/' prefix 65 | # - sim_value (optional, simulation only): the initial sim_value to give the tag; in the case of arrays, that sim_value will be given to all elements of the array 66 | # Example: eip_bridge/plc_tag_name OR eip_bridge/plc/tag/name OR eip_bridge/some_topic and tag key 67 | Subscribers: 68 | - name: 'eip_bridge/write_tag_int8' 69 | tag: 'int8_tag' 70 | type: 'Int8' 71 | length: 0 72 | sim_value: 7 73 | 74 | - name: 'eip_bridge/write_tag_int16' 75 | tag: 'pyThk.scanLen' 76 | type: 'Int16' 77 | length: 0 78 | sim_value: -10 79 | 80 | - name: 'eip_bridge/write_tag_int32' 81 | tag: 'int32_tag' 82 | type: 'Int32' 83 | length: 0 84 | sim_value: 1567 85 | 86 | - name: 'eip_bridge/write_tag_int64' 87 | tag: 'int64_tag' 88 | type: 'Int64' 89 | length: 0 90 | sim_value: 786 91 | 92 | - name: 'eip_bridge/write_tag_string' 93 | tag: 'string_tag' 94 | type: 'String' 95 | length: 0 96 | sim_value: 'foo' 97 | 98 | - name: 'eip_bridge/write_tag_array_int8' 99 | tag: 'int8_array_tag' 100 | type: 'Int8MultiArray' 101 | length: 5 102 | sim_value: 1 103 | 104 | - name: 'eip_bridge/write_tag_array_int16' 105 | tag: 'int16_array_tag' 106 | type: 'Int16MultiArray' 107 | length: 100 108 | sim_value: 15 109 | 110 | - name: 'eip_bridge/write_tag_array_int32' 111 | tag: 'int32_array_tag' 112 | type: 'Int32MultiArray' 113 | length: 5 114 | sim_value: -453 115 | 116 | - name: 'eip_bridge/write_tag_array_int64' 117 | tag: 'int64_array_tag' 118 | type: 'Int64MultiArray' 119 | length: 100 120 | sim_value: 13234 121 | 122 | # Publisher Fields: 123 | # - name: ROS Topic to publish to 124 | # - type: ROS std_msgs data type 125 | # - length: array length, or 0 if not an array 126 | # - tag (optional): plc tag name; otherwise plc tag name = topic name without 'eip_bridge/' prefix 127 | # - sim_value (optional, simulation only): the initial sim_value to give the tag; in the case of arrays, that sim_value will be given to all elements of the array 128 | # Example: eip_bridge/plc_tag_name OR eip_bridge/plc/tag/name OR eip_bridge/some_topic and tag key 129 | Publishers: 130 | - name: 'eip_bridge/read_tag_int8' 131 | tag: 'int8_tag' 132 | type: 'Int8' 133 | length: 0 134 | sim_value: 1 135 | 136 | - name: 'eip_bridge/pyThk/scanLen' 137 | tag: 'pyThk.scanLen' 138 | type: 'Int16' 139 | length: 0 140 | sim_value: 2 141 | 142 | - name: 'eip_bridge/read_tag_int32' 143 | tag: 'int32_tag' 144 | type: 'Int32' 145 | length: 0 146 | sim_value: 9876 147 | 148 | - name: 'eip_bridge/read_tag_int64' 149 | tag: 'int64_tag' 150 | type: 'Int64' 151 | length: 0 152 | sim_value: 325 153 | 154 | - name: 'eip_bridge/read_tag_string' 155 | tag: 'string_tag' 156 | type: 'String' 157 | length: 0 158 | sim_value: 'test' 159 | 160 | - name: 'eip_bridge/read_tag_array_int8' 161 | tag: 'int8_array_tag' 162 | type: 'Int8MultiArray' 163 | length: 5 164 | sim_value: 4 165 | 166 | - name: 'eip_bridge/read_tag_array_int16' 167 | tag: 'int16_array_tag' 168 | type: 'Int16MultiArray' 169 | length: 100 170 | sim_value: 353 171 | 172 | - name: 'eip_bridge/read_tag_array_int32' 173 | tag: 'int32_array_tag' 174 | type: 'Int32MultiArray' 175 | length: 5 176 | sim_value: 75 177 | 178 | - name: 'eip_bridge/read_tag_array_int64' 179 | tag: 'int64_array_tag' 180 | type: 'Int64MultiArray' 181 | length: 100 182 | sim_value: 6432 183 | -------------------------------------------------------------------------------- /eip_bridge/launch/eip_bridge.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /eip_bridge/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | eip_bridge 4 | 0.1.0 5 | Apache 2.0 6 | 7 | EIP bridge to/from ROS messages 8 | 9 | Michael Ripperger 10 | Bill McCormick 11 | Andrew Cunningham 12 | Jeremy Zoss 13 | 14 | catkin 15 | 16 | eip_msgs 17 | rospy 18 | 19 | -------------------------------------------------------------------------------- /eip_bridge/scripts/eip_bridge_node.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from eip_bridge.eip_bridge import EIPBridge 4 | from eip_bridge.eip_functions import EIPFunctions 5 | import rospy 6 | 7 | 8 | def main(): 9 | rospy.init_node('eip_bridge_node', anonymous=True) 10 | 11 | # Get the parameters 12 | try: 13 | ip = rospy.get_param('~ip') 14 | except: 15 | rospy.logfatal('Failed to get "ip" parameter') 16 | exit(1) 17 | 18 | try: 19 | config = rospy.get_param('~config') 20 | except: 21 | rospy.logfatal('Failed to get "config" parameter') 22 | exit(2) 23 | 24 | pub_rate = rospy.get_param('~pub_rate', 10) 25 | 26 | # Create the PLC 27 | plc = EIPFunctions() 28 | 29 | # Create the bridge 30 | bridge = EIPBridge(plc, ip, config, pub_rate) 31 | 32 | # Run the bridge 33 | bridge.run() 34 | rospy.spin() 35 | 36 | bridge.plc.disconnect_plc() 37 | 38 | 39 | if __name__ == '__main__': 40 | main() 41 | -------------------------------------------------------------------------------- /eip_bridge/scripts/eip_bridge_simulator_node.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from eip_bridge.eip_bridge import EIPBridge 4 | from eip_bridge.plc_simulator import PLCSimulator 5 | import rospy 6 | 7 | 8 | def main(): 9 | rospy.init_node('eip_bridge_node', anonymous=True) 10 | 11 | # Get the parameters 12 | try: 13 | config = rospy.get_param('~config') 14 | except: 15 | rospy.logfatal('Failed to get "config" parameter') 16 | exit(2) 17 | 18 | pub_rate = rospy.get_param('~pub_rate', 10) 19 | 20 | # Create the PLC 21 | plc = PLCSimulator() 22 | 23 | # Add tags to the simulated PLC 24 | plc.parse_config(config) 25 | 26 | # Create the bridge 27 | bridge = EIPBridge(plc, "0.0.0.0", config, pub_rate) 28 | 29 | # Run the bridge 30 | bridge.run() 31 | rospy.spin() 32 | 33 | bridge.plc.disconnect_plc() 34 | 35 | 36 | if __name__ == '__main__': 37 | main() 38 | -------------------------------------------------------------------------------- /eip_bridge/setup.py: -------------------------------------------------------------------------------- 1 | ## ! DO NOT MANUALLY INVOKE THIS setup.py, USE CATKIN INSTEAD 2 | 3 | from distutils.core import setup 4 | from catkin_pkg.python_setup import generate_distutils_setup 5 | 6 | # fetch values from package.xml 7 | setup_args = generate_distutils_setup( 8 | packages=['eip_bridge'], 9 | package_dir={'': 'src'}) 10 | 11 | setup(**setup_args) 12 | -------------------------------------------------------------------------------- /eip_bridge/src/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc -------------------------------------------------------------------------------- /eip_bridge/src/eip_bridge/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swri-robotics/eip_bridge/3beca24fb9f58cf2659058c128349a025dfc73b4/eip_bridge/src/eip_bridge/__init__.py -------------------------------------------------------------------------------- /eip_bridge/src/eip_bridge/eip_bridge.py: -------------------------------------------------------------------------------- 1 | from .eip_publisher import Publisher 2 | from .eip_subscriber import Subscriber 3 | from .eip_service import Service 4 | from eip_msgs.srv import SetMode 5 | from eip_msgs.srv import SetModeRequest 6 | from eip_msgs.srv import SetModeResponse 7 | 8 | from pycomm.cip.cip_base import DataError 9 | from pycomm.cip.cip_base import CommError 10 | import rospy 11 | from time import sleep 12 | 13 | 14 | class EIPBridge: 15 | """The main EIP Bridge communication object""" 16 | 17 | def __init__(self, plc, ip, config, pub_rate): 18 | self.plc = plc 19 | self.ip = ip 20 | self.config = config 21 | self.pub_rate = pub_rate 22 | self.timer = None 23 | 24 | # initialize empty topic and service lists 25 | self.subscribers = [] 26 | self.publishers = [] 27 | self.services = [] 28 | 29 | # parse config file 30 | if not self.parse_config(self.config): 31 | rospy.logfatal("Error initializing EIP Bridge") 32 | exit(1) 33 | 34 | # abort if no publishers, subscribers and services are specified 35 | if not self.subscribers and not self.publishers and not self.services: 36 | rospy.logfatal("No topics and services specified. Cowardly aborting with nothing to do.") 37 | exit(1) 38 | 39 | self.mode = SetModeRequest.RUN 40 | self.inform_mode() 41 | 42 | self.log_throttle = 60 43 | 44 | # Create a service server to set the mode of the PLC and bridge 45 | self.mode_service = "set_bridge_mode" 46 | self.mode_server = rospy.Service(self.mode_service, SetMode, self.set_mode) 47 | 48 | def parse_config(self, config): 49 | # Try to load the publishers 50 | rospy.loginfo("Attempting to load from 'Publishers' namespace") 51 | try: 52 | for cfg in config['Publishers']: 53 | try: 54 | pub = Publisher(cfg['name'], cfg['type'], cfg['tag'], cfg['length'], self.plc) 55 | self.publishers.append(pub) 56 | except KeyError as e: 57 | rospy.logwarn("No '{0}' entry defined in config file".format(e)) 58 | except Exception: 59 | rospy.logwarn("No publishers specified!") 60 | 61 | # Try to load the subscribers 62 | rospy.loginfo("Attempting to load from 'Subscribers' namespace") 63 | try: 64 | for cfg in config['Subscribers']: 65 | try: 66 | sub = Subscriber(cfg['name'], cfg['type'], cfg['tag'], cfg['length'], self.plc) 67 | self.subscribers.append(sub) 68 | except KeyError as e: 69 | rospy.logwarn("No '{0}' entry defined in config file".format(e)) 70 | except Exception: 71 | rospy.logwarn("No subscribers specified!") 72 | 73 | # Try to load the services 74 | rospy.loginfo("Attempting to load from 'Services' namespace") 75 | try: 76 | for cfg in config['Services']: 77 | try: 78 | srv = Service(cfg['name'], cfg['type'], self.plc) 79 | self.services.append(srv) 80 | 81 | except KeyError as e: 82 | rospy.logwarn("No '{0}' entry defined in config file".format(e)) 83 | except Exception: 84 | rospy.logwarn("No services specified!") 85 | 86 | # Return 87 | return True 88 | 89 | def inform_mode(self): 90 | if self.mode == SetModeRequest.RUN: 91 | rospy.loginfo("EIP Bridge is in RUN mode") 92 | elif self.mode == SetModeRequest.DEBUG: 93 | rospy.loginfo("EIP Bridge is in DEBUG mode") 94 | elif self.mode == SetModeRequest.SLEEP: 95 | rospy.loginfo("EIP Bridge is in SLEEP mode") 96 | return 97 | 98 | def run(self): 99 | self.timer = rospy.Timer(rospy.Duration(1.0 / self.pub_rate), self.update) 100 | 101 | def set_mode(self, req): 102 | if req.mode == self.mode: 103 | return SetModeResponse(True, "Bridge is already in desired state") 104 | 105 | # Check whether the timer updates should happen 106 | if not req.mode == SetModeRequest.RUN: 107 | # Stop the update callbacks 108 | self.timer.shutdown() 109 | 110 | # Disconnect from the PLC 111 | self.plc.disconnect_plc() 112 | 113 | # Record the mode internally 114 | self.mode = req.mode 115 | self.inform_mode() 116 | 117 | return SetModeResponse(True, "Stopped timer") 118 | else: 119 | if not self.plc.connect_plc(self.ip): 120 | self.inform_mode() 121 | return SetModeResponse(False, "Failed to connect to PLC") 122 | else: 123 | self.mode = req.mode 124 | self.inform_mode() 125 | 126 | # Restart the callbacks 127 | self.run() 128 | return SetModeResponse(True, "Started timer") 129 | 130 | def update(self, _): 131 | if not self.mode == SetModeRequest.RUN: 132 | rospy.logwarn_throttle(self.log_throttle, 133 | "Use the '{0}' service to change mode to RUN ({1})". 134 | format(self.mode_service, SetModeRequest.RUN)) 135 | return 136 | 137 | if not self.plc.is_connected(): 138 | try: 139 | rospy.loginfo("Connecting to PLC at {}".format(self.ip)) 140 | self.plc.connect_plc(self.ip) 141 | rospy.loginfo("Connected to PLC at {}".format(self.ip)) 142 | except CommError as e: 143 | rospy.logerr_throttle(self.log_throttle, "Failed to connect to PLC at {0}: {1}. Retry in 10 sec". 144 | format(self.ip, e)) 145 | sleep(10) 146 | return 147 | 148 | except Exception as e: 149 | rospy.logerr_throttle(self.log_throttle, "Error while connecting to PLC at {0}: {1}".format(self.ip, e)) 150 | return 151 | else: 152 | pub_topic = None 153 | try: 154 | for pub in self.publishers: 155 | pub_topic = pub.topic_name 156 | pub.publish() 157 | except DataError as e: 158 | rospy.logwarn("Data error while reading data for topic {2} from PLC at {0}: {1}". 159 | format(self.ip, e, pub_topic)) 160 | except CommError as e: 161 | rospy.logerr("Lost connection to PLC at {0}: {1}".format(self.ip, e)) 162 | except Exception as e: 163 | rospy.logerr("Exception while publishing topic {0}: {1}".format(pub_topic, e)) 164 | -------------------------------------------------------------------------------- /eip_bridge/src/eip_bridge/eip_functions.py: -------------------------------------------------------------------------------- 1 | import re 2 | from threading import Lock 3 | from pycomm.ab_comm.clx import Driver as ClxDriver 4 | 5 | 6 | def topic_to_tag(topic_name): 7 | # remove the leading topic id and replace /'s with .'s 8 | # and topic name becomes tag name 9 | plc_tag = re.sub('eip_bridge/', '', topic_name) 10 | plc_tag = re.sub('/', '.', plc_tag) 11 | 12 | return plc_tag 13 | 14 | 15 | def convert_topic_type(topic_type): 16 | # TODO: Add support for unsigned data types 17 | # AB PLC's do not have support for unsigned data types, 18 | # however with a little work some accommodation might be made 19 | # by mapping into the size data type. If pycomm then still returns a 20 | # negative value for the tag, it then must be ignored as in invalid. 21 | 22 | # TODO: allow support for uint64 by mapping to array LINT[2] 23 | 24 | if topic_type.find('Bool') >= 0: 25 | plc_tag_type = 'BOOL' 26 | elif topic_type.find('Int8') >= 0: 27 | plc_tag_type = 'SINT' 28 | # elif base_type == 'Int16' or base_type == 'UInt8': 29 | elif topic_type.find('Int16') >= 0: 30 | plc_tag_type = 'INT' 31 | # elif base_type == 'Int32' or base_type == 'UInt16': 32 | elif topic_type.find('Int32') >= 0: 33 | plc_tag_type = 'DINT' 34 | # elif base_type == 'Int64' or base_type == 'UInt32': 35 | elif topic_type.find('Int64') >= 0: 36 | plc_tag_type = 'LINT' 37 | elif topic_type.find('Float32') >= 0: 38 | plc_tag_type = 'REAL' 39 | elif topic_type.find('String') >= 0: 40 | plc_tag_type = 'STRING' 41 | else: 42 | raise ValueError("Not supported") 43 | 44 | return plc_tag_type 45 | 46 | 47 | class EIPFunctions: 48 | def __init__(self): 49 | # initialize a thread safe queue 50 | # load queue with a single plc obj 51 | self.plc = ClxDriver() 52 | self.mutex = Lock() 53 | 54 | def is_connected(self): 55 | with self.mutex: 56 | return self.plc.is_connected() 57 | 58 | def connect_plc(self, ip): 59 | with self.mutex: 60 | self.plc.open(ip) 61 | return self.plc.is_connected() 62 | 63 | def disconnect_plc(self): 64 | with self.mutex: 65 | if self.plc.is_connected(): 66 | self.plc.close() 67 | 68 | def read_tag(self, tag_name): 69 | with self.mutex: 70 | return self.plc.read_tag(tag_name)[0] 71 | 72 | def write_tag(self, tag_name, tag_value, tag_type): 73 | """ 74 | :param tag_name: tag name, or an array of tuple containing (tag name, value, data type) 75 | :param tag_value: the value to write or none if tag is an array of tuple or a tuple 76 | :param tag_type: the type of the tag to write or none if tag is an array of tuple or a tuple 77 | :return: None is returned in case of error otherwise the tag list is returned 78 | The type accepted are: 79 | - BOOL 80 | - SINT 81 | - INT' 82 | - DINT 83 | - REAL 84 | - LINT 85 | - BYTE 86 | - WORD 87 | - DWORD 88 | - LWORD 89 | """ 90 | with self.mutex: 91 | return self.plc.write_tag(tag_name, tag_value, tag_type) 92 | 93 | def read_tag_string(self, tag_name): 94 | with self.mutex: 95 | return self.plc.read_string(tag_name) 96 | 97 | def write_tag_string(self, tag_name, tag_value): 98 | with self.mutex: 99 | return self.plc.write_string(tag_name, tag_value) 100 | 101 | def read_tag_array(self, tag_name, count): 102 | with self.mutex: 103 | return self.plc.read_array(tag_name, count) 104 | 105 | def write_tag_array(self, tag_name, tag_array, tag_type): 106 | with self.mutex: 107 | self.plc.write_array(tag_name, tag_array, tag_type) 108 | 109 | # always return true; pycomm is not telling us if we succeed or not 110 | return True 111 | -------------------------------------------------------------------------------- /eip_bridge/src/eip_bridge/eip_publisher.py: -------------------------------------------------------------------------------- 1 | import rospy 2 | from .eip_functions import convert_topic_type, topic_to_tag 3 | from std_msgs.msg import * 4 | 5 | 6 | class Publisher: 7 | """ 8 | Publisher forwards messages from PLC to ROS 9 | """ 10 | def __init__(self, topic_name, topic_base_type, tag_name, length, plc): 11 | """Construct a Publisher object_instance """ 12 | 13 | self.topic_name = topic_name 14 | self.topic_type = topic_base_type 15 | self.length = length 16 | self.plc = plc 17 | self.pub = rospy.Publisher(self.topic_name, eval(self.topic_type), queue_size=1) 18 | 19 | try: 20 | self.plc_tag = tag_name or topic_to_tag(topic_name) 21 | except Exception as e: 22 | rospy.logerr("Error: setting publisher on topic {0}: {1}".format(topic_name, e)) 23 | raise 24 | 25 | try: 26 | self.plc_tag_type = convert_topic_type(topic_base_type) 27 | except Exception as e: 28 | rospy.logerr("Error: setting publisher on topic type {0}: {1}".format(topic_base_type, e)) 29 | raise 30 | 31 | def publish(self): 32 | """Publish ROS msg with PLC data""" 33 | 34 | if self.length > 0: 35 | tag_value = self.plc.read_tag_array(self.plc_tag, self.length) 36 | 37 | # if this is an array, tag_value will be a list of tuples (index, data) 38 | # use the map function below to separate the data from the index if required 39 | tag_value = map(lambda x: x[1], tag_value) 40 | 41 | elif self.plc_tag_type == 'STRING': 42 | tag_value = self.plc.read_tag_string(self.plc_tag) 43 | elif self.plc_tag_type == 'BOOL': 44 | tag_value = bool(self.plc.read_tag(self.plc_tag)) 45 | else: 46 | tag_value = self.plc.read_tag(self.plc_tag) 47 | 48 | # save of the message type 49 | msg_type = eval(self.topic_type) 50 | 51 | # make sure to convert/cast the data to be published to the correct type 52 | # using keyword 'data' to set the data; relying on the data type's 53 | # constructor using args will not always work (as in the case of *MultiArray's) 54 | msg = msg_type(data=tag_value) 55 | 56 | self.pub.publish(msg) 57 | -------------------------------------------------------------------------------- /eip_bridge/src/eip_bridge/eip_service.py: -------------------------------------------------------------------------------- 1 | import rospy 2 | import re 3 | from eip_msgs.srv import * 4 | from std_msgs.msg import * 5 | 6 | 7 | class Service: 8 | def __init__(self, service_name, service_type, plc): 9 | base_name = re.sub('eip_bridge/', '', service_name) 10 | self.plc = plc 11 | self.service_type = service_type 12 | self.request_type = eval(service_type + 'Request') 13 | self.response_type = eval(service_type + 'Response') 14 | 15 | # note that care must be taken given making service names 16 | rospy.Service(service_name, eval(service_type), getattr(self, 'handle_'+base_name)) 17 | 18 | def read_tag(self, req): 19 | tag_value = None 20 | error = 'OK' 21 | success = True 22 | 23 | try: 24 | if self.plc.is_connected(): 25 | tag_value = self.plc.read_tag(req.tag_name) 26 | 27 | if tag_value is None: 28 | raise Exception('Tag value is empty') 29 | 30 | except Exception as e: 31 | error = '{}'.format(e) 32 | success = False 33 | 34 | if tag_value is None: 35 | tag_value = 0 36 | success = False 37 | 38 | return self.response_type(tag_value, success, error) 39 | 40 | def read_tag_string(self, req): 41 | tag_value = None 42 | error = 'OK' 43 | success = True 44 | 45 | try: 46 | if self.plc.is_connected(): 47 | tag_value = self.plc.read_tag_string(req.tag_name) 48 | 49 | if tag_value is None: 50 | raise Exception('Tag value is empty') 51 | 52 | except Exception as e: 53 | error = '{}'.format(e) 54 | success = False 55 | 56 | if tag_value is None: 57 | tag_value = 0 58 | success = False 59 | 60 | return self.response_type(tag_value, success, error) 61 | 62 | def read_tag_array(self, req): 63 | tag_value = None 64 | error = 'OK' 65 | success = True 66 | 67 | try: 68 | if self.plc.is_connected(): 69 | tag_value = map(lambda x: x[1], self.plc.read_tag_array(req.tag_name, req.count)) 70 | 71 | if tag_value is None: 72 | raise Exception('Tag value is empty') 73 | 74 | if len(tag_value) == 0: 75 | raise Exception('"{}" tag has length of 0'.format(req.tag_name)) 76 | 77 | except Exception as e: 78 | error = '{}'.format(e) 79 | success = False 80 | 81 | return self.response_type(tag_value, success, error) 82 | 83 | def write_tag(self, req, typ): 84 | tag_list = None 85 | error = 'OK' 86 | success = True 87 | 88 | try: 89 | if self.plc.is_connected(): 90 | tag_list = self.plc.write_tag(req.tag_name, req.tag_value, typ) 91 | 92 | if tag_list is None: 93 | raise Exception('Tag list is empty') 94 | elif not tag_list: 95 | raise Exception('Failed to write to tag "{}"'.format(req.tag_name)) 96 | else: 97 | tag_list = req.tag_name 98 | 99 | except Exception as e: 100 | tag_list = '' 101 | error = '{}'.format(e) 102 | success = False 103 | 104 | return self.response_type(tag_list, success, error) 105 | 106 | def write_tag_string(self, req): 107 | tag_list = None 108 | error = 'OK' 109 | success = True 110 | 111 | try: 112 | if self.plc.is_connected(): 113 | tag_list = self.plc.write_tag_string(req.tag_name, req.tag_value) 114 | 115 | if tag_list is None: 116 | raise Exception('Tag list is empty') 117 | elif not tag_list: 118 | raise Exception('Failed to write to tag "{}"'.format(req.tag_name)) 119 | else: 120 | tag_list = req.tag_name 121 | 122 | except Exception as e: 123 | tag_list = '' 124 | error = '{}'.format(e) 125 | success = False 126 | 127 | return self.response_type(tag_list, success, error) 128 | 129 | def write_tag_array(self, req, typ): 130 | error = 'OK' 131 | success = True 132 | 133 | try: 134 | if self.plc.is_connected(): 135 | self.plc.write_tag_array(req.tag_name, list(req.tag_value), typ) 136 | 137 | except Exception as e: 138 | error = '{}'.format(e) 139 | success = False 140 | 141 | return self.response_type(success, error) 142 | 143 | def handle_read_tag_int8(self, req): 144 | return self.read_tag(req) 145 | 146 | def handle_read_tag_int16(self, req): 147 | return self.read_tag(req) 148 | 149 | def handle_read_tag_int32(self, req): 150 | return self.read_tag(req) 151 | 152 | def handle_read_tag_int64(self, req): 153 | return self.read_tag(req) 154 | 155 | def handle_read_tag_string(self, req): 156 | return self.read_tag_string(req) 157 | 158 | def handle_read_tag_float32(self, req): 159 | return self.read_tag(req) 160 | 161 | def handle_read_tag_array_int8(self, req): 162 | return self.read_tag_array(req) 163 | 164 | def handle_read_tag_array_int16(self, req): 165 | return self.read_tag_array(req) 166 | 167 | def handle_read_tag_array_int32(self, req): 168 | return self.read_tag_array(req) 169 | 170 | def handle_read_tag_array_int64(self, req): 171 | return self.read_tag_array(req) 172 | 173 | def handle_read_tag_array_float32(self, req): 174 | return self.read_tag_array(req) 175 | 176 | def handle_read_tag_bool(self, req): 177 | return self.read_tag(req) 178 | 179 | def handle_write_tag_int8(self, req): 180 | return self.write_tag(req, 'SINT') 181 | 182 | def handle_write_tag_int16(self, req): 183 | return self.write_tag(req, 'INT') 184 | 185 | def handle_write_tag_int32(self, req): 186 | return self.write_tag(req, 'DINT') 187 | 188 | def handle_write_tag_int64(self, req): 189 | return self.write_tag(req, 'LINT') 190 | 191 | def handle_write_tag_float32(self, req): 192 | return self.write_tag(req, 'REAL') 193 | 194 | def handle_write_tag_string(self, req): 195 | return self.write_tag_string(req) 196 | 197 | def handle_write_tag_array_int8(self, req): 198 | return self.write_tag_array(req, 'SINT') 199 | 200 | def handle_write_tag_array_int16(self, req): 201 | return self.write_tag_array(req, 'INT') 202 | 203 | def handle_write_tag_array_int32(self, req): 204 | return self.write_tag_array(req, 'DINT') 205 | 206 | def handle_write_tag_array_int64(self, req): 207 | return self.write_tag_array(req, 'LINT') 208 | 209 | def handle_write_tag_array_float32(self, req): 210 | return self.write_tag_array(req, 'REAL') 211 | 212 | def handle_write_tag_bool(self, req): 213 | return self.write_tag(req, 'BOOL') 214 | -------------------------------------------------------------------------------- /eip_bridge/src/eip_bridge/eip_subscriber.py: -------------------------------------------------------------------------------- 1 | import rospy 2 | from .eip_functions import convert_topic_type, topic_to_tag 3 | from std_msgs.msg import * 4 | 5 | 6 | class Subscriber: 7 | """ 8 | Subscriber read messages from ROS and update PLC 9 | """ 10 | def __init__(self, topic_name, topic_base_type, tag_name, length, plc): 11 | """Construct a Subscriber object_instance """ 12 | 13 | self.topic_name = topic_name 14 | self.topic_type = topic_base_type 15 | self.length = length 16 | self.plc = plc 17 | 18 | try: 19 | self.plc_tag = tag_name or topic_to_tag(topic_name) 20 | except Exception as e: 21 | rospy.logerr("Error: setting subscriber on topic {0}: {1}".format(topic_name, e)) 22 | raise 23 | 24 | try: 25 | self.plc_tag_type = convert_topic_type(topic_base_type) 26 | except Exception as e: 27 | rospy.logerr("Error: setting subscriber on topic type {0}: {1}".format(topic_base_type, e)) 28 | raise 29 | 30 | rospy.Subscriber(topic_name, eval(topic_base_type), self.callback, queue_size=1) 31 | 32 | def callback(self, msg): 33 | """Update EIP data when a new ROS message is received""" 34 | result = None 35 | try: 36 | if self.length == 0: 37 | if self.plc_tag_type == 'STRING': 38 | result = self.plc.write_tag_string(self.plc_tag, msg.data) 39 | # elif self.plc_tag_type == 'BOOL': 40 | # result = self.plc.write_tag(self.plc_tag, int(msg.data), self.plc_tag_type) 41 | else: 42 | result = self.plc.write_tag(self.plc_tag, msg.data, self.plc_tag_type) 43 | else: 44 | result = self.plc.write_tag_array(self.plc_tag, list(msg.data), self.plc_tag_type) 45 | except Exception as e: 46 | result = '{}'.format(e) 47 | finally: 48 | return result 49 | -------------------------------------------------------------------------------- /eip_bridge/src/eip_bridge/plc_simulator.py: -------------------------------------------------------------------------------- 1 | import rospy 2 | 3 | 4 | class Tag: 5 | def __init__(self, name, value=None): 6 | self.name = name 7 | self.value = value 8 | 9 | def get_name(self): 10 | return self.name 11 | 12 | def get_value(self): 13 | return self.value 14 | 15 | def set_value(self, value): 16 | self.value = value 17 | 18 | 19 | class PLCSimulator: 20 | def __init__(self): 21 | self.tags = {} 22 | self.active = False 23 | 24 | def is_connected(self): 25 | return self.active 26 | 27 | def connect_plc(self, ip): 28 | self.active = True 29 | return self.active 30 | 31 | def disconnect_plc(self): 32 | self.active = False 33 | 34 | def add_tag(self, tag_name, length, value=None): 35 | tag = Tag(tag_name, value) 36 | if length > 0: 37 | tag.set_value(zip(range(length), [value for value in range(0, length)])) 38 | self.tags[tag_name] = tag 39 | 40 | def parse_config(self, config): 41 | # Try to load tags from the publishers namespace 42 | for ns in config: 43 | # Try to load the entries under each namespace 44 | try: 45 | entry = config[ns] 46 | 47 | rospy.loginfo("Attempting to load tags from '{0}' namespace".format(ns)) 48 | for cfg in entry: 49 | try: 50 | tag_name = cfg['tag'] 51 | length = cfg['length'] 52 | 53 | try: 54 | value = cfg['sim_value'] 55 | except KeyError as value_ke: 56 | rospy.logwarn("'{0}' entry does not exist for '{1}'; setting '{0}' to 'None'" 57 | .format(value_ke, tag_name)) 58 | value = None 59 | 60 | self.add_tag(tag_name, length, value) 61 | 62 | except KeyError as ke: 63 | rospy.logwarn("'{0}' entry does not exist".format(ke)) 64 | except Exception as ex: 65 | rospy.logwarn("Exception: {0}".format(ex)) 66 | 67 | except Exception: 68 | rospy.logwarn("No tags defined in '{0}' namespace".format(ns)) 69 | 70 | def write_to_tag(self, tag_name, value): 71 | if self.is_connected(): 72 | try: 73 | tag = self.tags[tag_name] 74 | tag.setValue(value) 75 | return tag_name 76 | 77 | except KeyError as e: 78 | raise Exception('Tag "{}" does not exist'.format(e)) 79 | 80 | else: 81 | raise Exception('PLC is not currently connected') 82 | 83 | def read_from_tag(self, tag_name): 84 | if self.is_connected(): 85 | try: 86 | tag = self.tags[tag_name] 87 | return tag.get_value() 88 | 89 | except KeyError as e: 90 | raise Exception('Tag "{}" does not exist'.format(e)) 91 | 92 | else: 93 | raise Exception('PLC is not currently connected') 94 | 95 | def read_tag_array(self, tag_name, length): 96 | return self.read_from_tag(tag_name) 97 | 98 | def read_tag(self, tag_name): 99 | return self.read_from_tag(tag_name) 100 | 101 | def read_tag_string(self, tag_name): 102 | return self.read_from_tag(tag_name) 103 | 104 | def write_tag_array(self, tag_name, value_list, tag_type): 105 | # Value is a list, but needs to be stored as a tuple 106 | value_tuple = zip(range(len(value_list)), value_list) 107 | return self.write_to_tag(tag_name, value_tuple) 108 | 109 | def write_tag(self, tag_name, value, tag_type): 110 | return self.write_to_tag(tag_name, value) 111 | 112 | def write_tag_string(self, tag_name, value): 113 | return self.write_to_tag(tag_name, value) 114 | -------------------------------------------------------------------------------- /eip_msgs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(eip_msgs) 3 | 4 | find_package(catkin REQUIRED COMPONENTS 5 | message_generation 6 | ) 7 | 8 | add_service_files( 9 | FILES 10 | ReadTagArrayInt8.srv 11 | ReadTagArrayInt16.srv 12 | ReadTagArrayInt32.srv 13 | ReadTagArrayInt64.srv 14 | ReadTagArrayFloat32.srv 15 | ReadTagBool.srv 16 | ReadTagInt8.srv 17 | ReadTagInt16.srv 18 | ReadTagInt32.srv 19 | ReadTagInt64.srv 20 | ReadTagString.srv 21 | ReadTagFloat32.srv 22 | WriteTagArrayInt8.srv 23 | WriteTagArrayInt16.srv 24 | WriteTagArrayInt32.srv 25 | WriteTagArrayInt64.srv 26 | WriteTagArrayFloat32.srv 27 | WriteTagBool.srv 28 | WriteTagInt8.srv 29 | WriteTagInt16.srv 30 | WriteTagInt32.srv 31 | WriteTagInt64.srv 32 | WriteTagFloat32.srv 33 | WriteTagString.srv 34 | SetMode.srv 35 | ) 36 | 37 | generate_messages() 38 | 39 | catkin_package(CATKIN_DEPENDS message_runtime) 40 | -------------------------------------------------------------------------------- /eip_msgs/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | eip_msgs 4 | 0.1.0 5 | EIP bridge messages to/from ROS messages 6 | Michael Ripperger 7 | Apache 2.0 8 | Bill McCormick 9 | Andrew Cunningham 10 | Jeremy Zoss 11 | catkin 12 | message_generation 13 | message_runtime 14 | 15 | -------------------------------------------------------------------------------- /eip_msgs/srv/ReadTagArrayFloat32.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | int32 count 3 | --- 4 | float32[] tag_value 5 | bool success 6 | string msg 7 | -------------------------------------------------------------------------------- /eip_msgs/srv/ReadTagArrayInt16.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | int32 count 3 | --- 4 | int16[] tag_value 5 | bool success 6 | string msg 7 | -------------------------------------------------------------------------------- /eip_msgs/srv/ReadTagArrayInt32.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | int32 count 3 | --- 4 | int32[] tag_value 5 | bool success 6 | string msg 7 | -------------------------------------------------------------------------------- /eip_msgs/srv/ReadTagArrayInt64.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | int32 count 3 | --- 4 | int64[] tag_value 5 | bool success 6 | string msg 7 | -------------------------------------------------------------------------------- /eip_msgs/srv/ReadTagArrayInt8.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | int32 count 3 | --- 4 | int8[] tag_value 5 | bool success 6 | string msg 7 | -------------------------------------------------------------------------------- /eip_msgs/srv/ReadTagBool.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | --- 3 | bool tag_value 4 | bool success 5 | string msg 6 | -------------------------------------------------------------------------------- /eip_msgs/srv/ReadTagFloat32.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | --- 3 | float32 tag_value 4 | bool success 5 | string msg 6 | -------------------------------------------------------------------------------- /eip_msgs/srv/ReadTagInt16.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | --- 3 | int16 tag_value 4 | bool success 5 | string msg 6 | -------------------------------------------------------------------------------- /eip_msgs/srv/ReadTagInt32.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | --- 3 | int32 tag_value 4 | bool success 5 | string msg 6 | -------------------------------------------------------------------------------- /eip_msgs/srv/ReadTagInt64.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | --- 3 | int64 tag_value 4 | bool success 5 | string msg 6 | -------------------------------------------------------------------------------- /eip_msgs/srv/ReadTagInt8.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | --- 3 | int8 tag_value 4 | bool success 5 | string msg 6 | -------------------------------------------------------------------------------- /eip_msgs/srv/ReadTagString.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | --- 3 | string tag_value 4 | bool success 5 | string msg 6 | -------------------------------------------------------------------------------- /eip_msgs/srv/SetMode.srv: -------------------------------------------------------------------------------- 1 | # Request 2 | int8 RUN = 0 3 | int8 DEBUG = 1 4 | int8 SLEEP = 2 5 | 6 | int8 mode 7 | 8 | --- 9 | # Response 10 | bool success 11 | string msg 12 | -------------------------------------------------------------------------------- /eip_msgs/srv/WriteTagArrayFloat32.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | float32[] tag_value 3 | --- 4 | bool success 5 | string msg 6 | -------------------------------------------------------------------------------- /eip_msgs/srv/WriteTagArrayInt16.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | int16[] tag_value 3 | --- 4 | bool success 5 | string msg 6 | -------------------------------------------------------------------------------- /eip_msgs/srv/WriteTagArrayInt32.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | int32[] tag_value 3 | --- 4 | bool success 5 | string msg 6 | -------------------------------------------------------------------------------- /eip_msgs/srv/WriteTagArrayInt64.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | int64[] tag_value 3 | --- 4 | bool success 5 | string msg 6 | -------------------------------------------------------------------------------- /eip_msgs/srv/WriteTagArrayInt8.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | int8[] tag_value 3 | --- 4 | bool success 5 | string msg 6 | -------------------------------------------------------------------------------- /eip_msgs/srv/WriteTagBool.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | bool tag_value 3 | --- 4 | string tag_list 5 | bool success 6 | string msg 7 | -------------------------------------------------------------------------------- /eip_msgs/srv/WriteTagFloat32.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | float32 tag_value 3 | --- 4 | string tag_list 5 | bool success 6 | string msg 7 | -------------------------------------------------------------------------------- /eip_msgs/srv/WriteTagInt16.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | int16 tag_value 3 | --- 4 | string tag_list 5 | bool success 6 | string msg 7 | -------------------------------------------------------------------------------- /eip_msgs/srv/WriteTagInt32.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | int32 tag_value 3 | --- 4 | string tag_list 5 | bool success 6 | string msg 7 | -------------------------------------------------------------------------------- /eip_msgs/srv/WriteTagInt64.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | int64 tag_value 3 | --- 4 | string tag_list 5 | bool success 6 | string msg 7 | -------------------------------------------------------------------------------- /eip_msgs/srv/WriteTagInt8.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | int8 tag_value 3 | --- 4 | string tag_list 5 | bool success 6 | string msg 7 | -------------------------------------------------------------------------------- /eip_msgs/srv/WriteTagString.srv: -------------------------------------------------------------------------------- 1 | string tag_name 2 | string tag_value 3 | --- 4 | string tag_list 5 | bool success 6 | string msg 7 | --------------------------------------------------------------------------------