ciplink 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. ciplink-0.1.0/LICENSE +24 -0
  2. ciplink-0.1.0/MANIFEST.in +2 -0
  3. ciplink-0.1.0/PKG-INFO +302 -0
  4. ciplink-0.1.0/README.rst +279 -0
  5. ciplink-0.1.0/ciplink/__init__.py +40 -0
  6. ciplink-0.1.0/ciplink/_logix_bundle_codec.py +108 -0
  7. ciplink-0.1.0/ciplink/_logix_metadata_bundle.py +32 -0
  8. ciplink-0.1.0/ciplink/_version.py +26 -0
  9. ciplink-0.1.0/ciplink/cip/__init__.py +29 -0
  10. ciplink-0.1.0/ciplink/cip/data_types.py +1227 -0
  11. ciplink-0.1.0/ciplink/cip/object_library.py +191 -0
  12. ciplink-0.1.0/ciplink/cip/pccc.py +170 -0
  13. ciplink-0.1.0/ciplink/cip/services.py +117 -0
  14. ciplink-0.1.0/ciplink/cip/status_info.py +1732 -0
  15. ciplink-0.1.0/ciplink/cip_driver.py +706 -0
  16. ciplink-0.1.0/ciplink/const.py +67 -0
  17. ciplink-0.1.0/ciplink/custom_types.py +235 -0
  18. ciplink-0.1.0/ciplink/exceptions.py +59 -0
  19. ciplink-0.1.0/ciplink/logger.py +90 -0
  20. ciplink-0.1.0/ciplink/logix_auth.py +196 -0
  21. ciplink-0.1.0/ciplink/logix_descriptions.py +268 -0
  22. ciplink-0.1.0/ciplink/logix_driver.py +2380 -0
  23. ciplink-0.1.0/ciplink/logix_metadata.py +185 -0
  24. ciplink-0.1.0/ciplink/map.py +114 -0
  25. ciplink-0.1.0/ciplink/packets/__init__.py +63 -0
  26. ciplink-0.1.0/ciplink/packets/base.py +197 -0
  27. ciplink-0.1.0/ciplink/packets/cip.py +136 -0
  28. ciplink-0.1.0/ciplink/packets/ethernetip.py +234 -0
  29. ciplink-0.1.0/ciplink/packets/logix.py +501 -0
  30. ciplink-0.1.0/ciplink/packets/util.py +250 -0
  31. ciplink-0.1.0/ciplink/py.typed +0 -0
  32. ciplink-0.1.0/ciplink/slc_driver.py +773 -0
  33. ciplink-0.1.0/ciplink/socket_.py +86 -0
  34. ciplink-0.1.0/ciplink/tag.py +49 -0
  35. ciplink-0.1.0/ciplink/util.py +62 -0
  36. ciplink-0.1.0/ciplink.egg-info/PKG-INFO +302 -0
  37. ciplink-0.1.0/ciplink.egg-info/SOURCES.txt +40 -0
  38. ciplink-0.1.0/ciplink.egg-info/dependency_links.txt +1 -0
  39. ciplink-0.1.0/ciplink.egg-info/requires.txt +3 -0
  40. ciplink-0.1.0/ciplink.egg-info/top_level.txt +1 -0
  41. ciplink-0.1.0/pyproject.toml +39 -0
  42. ciplink-0.1.0/setup.cfg +4 -0
ciplink-0.1.0/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Rowan Ramamurthy
4
+ Copyright (c) 2020 Ian Ottoway
5
+ Copyright (c) 2014 Agostino Ruscito
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
24
+
@@ -0,0 +1,2 @@
1
+ include README.rst
2
+ include LICENSE
ciplink-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,302 @@
1
+ Metadata-Version: 2.4
2
+ Name: ciplink
3
+ Version: 0.1.0
4
+ Summary: Ethernet/IP library for Allen-Bradley PLCs, a pycomm3 fork with instance-ID reads and tag-table change detection.
5
+ Author-email: Rowan Ramamurthy <rramamurthy@factoryautomation.com>
6
+ License-Expression: MIT
7
+ Project-URL: Source, https://github.com/RowanRama/ciplink
8
+ Project-URL: Upstream, https://github.com/ottowayi/pycomm3
9
+ Project-URL: Documentation, https://docs.pycomm3.dev
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Manufacturing
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator
16
+ Classifier: Topic :: Scientific/Engineering :: Human Machine Interfaces
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/x-rst
19
+ License-File: LICENSE
20
+ Provides-Extra: tests
21
+ Requires-Dist: pytest; extra == "tests"
22
+ Dynamic: license-file
23
+
24
+ =======
25
+ ciplink
26
+ =======
27
+
28
+ .. <<start>>
29
+
30
+ .. image:: https://img.shields.io/pypi/v/ciplink.svg?style=for-the-badge
31
+ :target: https://pypi.org/project/ciplink/
32
+ :alt: PyPI Version
33
+
34
+ .. image:: https://img.shields.io/pypi/l/ciplink.svg?style=for-the-badge
35
+ :target: https://pypi.org/project/ciplink/
36
+ :alt: License
37
+
38
+ .. image:: https://img.shields.io/pypi/pyversions/ciplink.svg?style=for-the-badge
39
+ :target: https://pypi.org/project/ciplink/
40
+ :alt: Python Versions
41
+
42
+ |
43
+
44
+ Introduction
45
+ ============
46
+
47
+ ``ciplink`` is a fork of `pycomm3`_ by Ian Ottoway, a Python library for communicating with
48
+ Allen-Bradley PLCs using Ethernet/IP. It keeps the pycomm3 API (import ``ciplink`` instead of
49
+ ``pycomm3``) and adds:
50
+
51
+ - tag requests addressed by symbol instance ID, including UDT members and program-scoped tags
52
+ - tag-table change detection with the controller's edit counter, so online edits trigger a
53
+ reload instead of reading a reused instance ID
54
+ - fast reloads, batched tag-list uploads and a restart cache (``tag_cache_file``)
55
+ - Add-On Instruction access probing: whole AOI reads return their readable parameters, and
56
+ AOI locals are refused without a request
57
+
58
+ Without the work of the pycomm3 developers, and of the original `pycomm`_ developers before
59
+ them, ``ciplink`` would not exist. The pycomm3 `documentation`_ applies to ciplink apart from
60
+ the import name.
61
+
62
+
63
+ .. _pycomm3: https://github.com/ottowayi/pycomm3
64
+
65
+ .. _pycomm: https://github.com/ruscito/pycomm
66
+
67
+ .. _documentation: https://docs.pycomm3.dev
68
+
69
+
70
+ Drivers
71
+ =======
72
+
73
+ ``ciplink`` includes 3 drivers:
74
+
75
+ - `CIPDriver`_
76
+ This driver is the base driver for the library, it handles common CIP services used
77
+ by the other drivers. Things like opening/closing a connection, register/unregister sessions,
78
+ forward open/close services, device discovery, and generic messaging. It can be used to connect to
79
+ any Ethernet/IP device, like: drives, switches, meters, and other non-PLC devices.
80
+ ``ciplink`` is a client for explicit messaging only (unconnected/UCMM and class 3 connected over TCP 44818);
81
+ implicit class 0/1 I/O (UDP 2222), produced/consumed tags and acting as a CIP server/adapter are not supported.
82
+
83
+ - `LogixDriver`_
84
+ This driver supports services specific to ControlLogix, CompactLogix, and Micro800 PLCs.
85
+ Services like reading/writing tags, uploading the tag list, and getting/setting the PLC time.
86
+
87
+ - `SLCDriver`_
88
+ This driver supports basic reading/writing data files in a SLC500 or MicroLogix PLCs. It is
89
+ a port of the ``SlcDriver`` from ``pycomm`` with minimal changes to make the API similar to the
90
+ other drivers. Currently this driver is considered legacy and it's development will be on
91
+ a limited basis. PLC-5 processors are not supported by any driver.
92
+
93
+ .. _CIPDriver: https://docs.pycomm3.dev/en/latest/usage/cipdriver.html
94
+
95
+ .. _LogixDriver: https://docs.pycomm3.dev/en/latest/usage/logixdriver.html
96
+
97
+ .. _SLCDriver: https://docs.pycomm3.dev/en/latest/usage/slcdriver.html
98
+
99
+ Disclaimer
100
+ ==========
101
+
102
+ PLCs can be used to control heavy or dangerous equipment, this library is provided "as is" and makes no guarantees on
103
+ its reliability in a production environment. This library makes no promises in the completeness or correctness of the
104
+ protocol implementations and should not be solely relied upon for critical systems. The development for this library
105
+ is aimed at providing quick and convenient access for reading/writing data inside Allen-Bradley PLCs.
106
+
107
+
108
+ Setup
109
+ =====
110
+
111
+ The package can be installed from `PyPI`_ using ``pip``: ``pip install ciplink`` or ``python -m pip install ciplink``.
112
+
113
+ .. _PyPI: https://pypi.org/project/ciplink/
114
+
115
+ Optionally, you may configure logging using the Python standard `logging`_ library. A convenience method is provided
116
+ to help configure basic logging, see the `Logging Section`_ in the docs for more information.
117
+
118
+ .. _logging: https://docs.python.org/3/library/logging.html
119
+
120
+ .. _Logging Section: https://docs.pycomm3.dev/en/latest/getting_started.html#logging
121
+
122
+
123
+ Python and OS Support
124
+ =====================
125
+
126
+ ``ciplink`` is a Python 3-only library and is supported on Python 3.9 and newer.
127
+ There should be no OS-specific requirements and should be able to run on any OS that Python is supported on.
128
+ Development and testing is done primarily on Windows 10. If you encounter an OS-related problem, please open an issue
129
+ in the `GitHub repository`_ and it will be investigated.
130
+
131
+ .. attention::
132
+
133
+ Python 3.6.0 is not supported due to ``NamedTuple`` not supporting
134
+ `default values and methods <https://docs.python.org/3/library/typing.html#typing.NamedTuple>`_ until 3.6.1
135
+
136
+ .. _GitHub repository: https://github.com/ottowayi/pycomm3
137
+
138
+ .. <<end>>
139
+
140
+ Documentation
141
+ =============
142
+
143
+ This README covers a basic overview of the library, full documentation can be found on
144
+ `Read the Docs`_ or by visiting `https://pycomm3.dev <https://pycomm3.dev>`_.
145
+
146
+ .. _Read the Docs: https://pycomm3.readthedocs.io/en/latest/
147
+
148
+ Contributions
149
+ =============
150
+
151
+ If you'd like to contribute or are having an issue, please read the `Contributing`_ guidelines.
152
+
153
+ .. _Contributing: CONTRIBUTING.md
154
+
155
+
156
+ Highlighted Features
157
+ ====================
158
+
159
+ - ``generic_message`` for extra functionality not directly implemented
160
+ - working similar to the MSG instruction in Logix, arguments similar to the MESSAGE properties
161
+ - See the examples section for things like getting/setting drive parameters, IP configuration, or uploading an EDS file
162
+ - used internally to implement some of the other methods (get/set_plc_time, forward open/close, etc)
163
+ - simplified data types
164
+ - allows use of standard Python types by abstracting CIP implementation details away from the user
165
+ - strings use normal Python ``str`` objects, does not require handling of the ``LEN`` and ``DATA`` attributes separately
166
+ - custom string types are also identified automatically and not limited to just the builtin one
167
+ - BOOL arrays use normal Python ``bool`` objects, does not require complicated bit shifting of the DWORD value
168
+ - powerful type system to allow types to represent any CIP object and handle encoding/decoding the object
169
+
170
+ LogixDriver
171
+ -----------
172
+
173
+ - simple API, only 1 ``read`` method and 1 ``write`` method for tags.
174
+ - does not require using different methods for different data types
175
+ - requires the tag name only, no other information required from the user
176
+ - automatically manages request/response size to pack as many requests into a single packet
177
+ - automatically handles fragmented requests for large tags that can't fit in a single packet
178
+ - both support full structure reading/writing (UDTs, AOIs, etc)
179
+ - for ``read`` the ``Tag.value`` will be a ``dict`` of ``{attribute: value}``
180
+ - for ``write`` the value should be a dict of ``{attribute: value}`` , nesting as needed
181
+ - does not do partial writes, the value must match the complete structure
182
+ - not recommended for builtin type (TIMER, CONTROL, COUNTER, etc)
183
+ - both require no attributes to have an External Access of None
184
+ - uploads the tag list and data type definitions from the PLC
185
+ - no requirement for user to determine tags available (like from an L5X export)
186
+ - definitions are required for ``read``/``write`` methods
187
+ - automatically enables/disables different features based on the target PLC
188
+ - Extended Forward Open (EN2T or newer and v20+)
189
+ - Symbol Instance Addressing (Logix v21+)
190
+ - detection of Micro800 and disables unsupported features (CIP Path, Ex. Forward Open, Instance Addressing, etc)
191
+
192
+ LogixDriver Overview
193
+ ====================
194
+
195
+ Creating a driver is simple, only a ``path`` argument is required. The ``path`` can be the IP address, IP and slot,
196
+ or a full CIP route, refer to the documentation for more details. The example below shows how to create a simple
197
+ driver and print some of the information collected about the device.
198
+
199
+ ::
200
+
201
+ from ciplink import LogixDriver
202
+
203
+ with LogixDriver('10.20.30.100/1') as plc:
204
+ print(plc)
205
+ # OUTPUT:
206
+ # Program Name: PLCA, Device: 1756-L83E/B, Revision: 28.13
207
+
208
+ print(plc.info)
209
+ # OUTPUT:
210
+ # {'vendor': 'Rockwell Automation/Allen-Bradley', 'product_type': 'Programmable Logic Controller',
211
+ # 'product_code': 166, 'revision': {'major': 28, 'minor': 13}, 'status': b'`0', 'serial': 'ffffffff',
212
+ # 'product_name': '1756-L83E/B', 'keyswitch': 'REMOTE RUN', 'name': 'PLCA'}
213
+
214
+
215
+ Reading/Writing Tags
216
+ --------------------
217
+
218
+ Reading or writing tags is as simple as calling the ``read`` and ``write`` methods. Both methods accept any number of tags,
219
+ and will automatically pack multiple tags into a *Multiple Service Packet Service (0x0A)* while making sure to stay below the connection size.
220
+ If there is a tag value that cannot fit within the request/reply packet, it will automatically handle that tag independently
221
+ using the *Read Tag Fragmented (0x52)* or *Write Tag Fragmented (0x53)* requests.
222
+
223
+ Both methods will return ``Tag`` objects to reflect the success or failure of the operation.
224
+
225
+ ::
226
+
227
+ class Tag(NamedTuple):
228
+ tag: str # the name of the tag, does not include ``{<# elements>}`` from request
229
+ value: Any # value read or written, may be ``None`` if an error occurred
230
+ type: Optional[str] = None # data type of tag, including ``[<# elements>]`` from request
231
+ error: Optional[str] = None # ``None`` if successful, else the CIP error or exception thrown
232
+
233
+ ``Tag`` objects are considered successful (truthy) if the ``value`` is not ``None`` and the ``error`` is ``None``.
234
+
235
+
236
+ Examples::
237
+
238
+ with LogixDriver('10.20.30.100') as plc:
239
+ plc.read('tag1', 'tag2', 'tag3') # read multiple tags
240
+ plc.read('array{10}') # read 10 elements starting at 0 from an array
241
+ plc.read('array[5]{20}') # read 20 elements starting at elements 5 from an array
242
+ plc.read('string_tag') # read a string tag and get a string
243
+ plc.read('a_udt_tag') # the response .value will be a dict like: {'attr1`: 1, 'attr2': 'a string', ...}
244
+
245
+ # writes require a sequence of tuples of [(tag name, value), ... ]
246
+ plc.write('tag1', 0) # single writes do not need to be passed as a tuple
247
+ plc.write(('tag1', 0), ('tag2', 1), ('tag3', 2)) # write multiple tags
248
+ plc.write(('array{5}', [1, 2, 3, 4, 5])) # write 5 elements to an array starting at the 0 element
249
+ plc.write('array[10]{5}', [1, 2, 3, 4, 5]) # write 5 elements to an array starting at element 10
250
+ plc.write('string_tag', 'Hello World!') # write to a string tag with a string
251
+ plc.write('string_array[2]{5}', 'Write an array of strings'.split()) # write an array of 5 strings starting at element 2
252
+ plc.write('a_udt_tag', {'attr1': 1, 'attr2': 'a string', ...}) # can also use a dict to write a struct
253
+
254
+ # Check the results
255
+ results = plc.read('tag1', 'tag2', 'tag3')
256
+ if all(results):
257
+ print('They all worked!')
258
+ else:
259
+ for result in results:
260
+ if not result:
261
+ print(f'Reading tag {result.tag} failed with error: {result.error}')
262
+
263
+ .. Note::
264
+
265
+ Tag names for both ``read`` and ``write`` are matched without regard to case (an exact match is tried first).
266
+
267
+
268
+ Unit Testing
269
+ ============
270
+
271
+ ``pytest`` is used for unit testing. The ``tests`` directory contains an L5X export of the testing program
272
+ that contains all tags necessary for testing. The only requirement for testing (besides a running PLC with the testing
273
+ program) is the environment variable ``PLCPATH`` for the PLC defined.
274
+
275
+ User Tests
276
+ ----------
277
+
278
+ These tests are for users to run. There are a few tests that are specific to a demo
279
+ plc, those are excluded. To run them you have the following options:
280
+
281
+ with `tox`:
282
+
283
+ - modify the ``PLCPATH`` variable in ``tox.ini``
284
+ - then run this command: ``tox -e user``
285
+
286
+ or with ``pytest``:
287
+
288
+ .. code-block::
289
+
290
+ set PLCPATH=192.168.1.100
291
+ pytest --ignore tests/online/test_demo_plc.py
292
+
293
+ *(or the equivalent in your shell)*
294
+
295
+
296
+ .. Note::
297
+ Test coverage is not complete, pull requests are welcome to help improve coverage.
298
+
299
+
300
+ License
301
+ =======
302
+ ``ciplink`` is distributed under the MIT License
@@ -0,0 +1,279 @@
1
+ =======
2
+ ciplink
3
+ =======
4
+
5
+ .. <<start>>
6
+
7
+ .. image:: https://img.shields.io/pypi/v/ciplink.svg?style=for-the-badge
8
+ :target: https://pypi.org/project/ciplink/
9
+ :alt: PyPI Version
10
+
11
+ .. image:: https://img.shields.io/pypi/l/ciplink.svg?style=for-the-badge
12
+ :target: https://pypi.org/project/ciplink/
13
+ :alt: License
14
+
15
+ .. image:: https://img.shields.io/pypi/pyversions/ciplink.svg?style=for-the-badge
16
+ :target: https://pypi.org/project/ciplink/
17
+ :alt: Python Versions
18
+
19
+ |
20
+
21
+ Introduction
22
+ ============
23
+
24
+ ``ciplink`` is a fork of `pycomm3`_ by Ian Ottoway, a Python library for communicating with
25
+ Allen-Bradley PLCs using Ethernet/IP. It keeps the pycomm3 API (import ``ciplink`` instead of
26
+ ``pycomm3``) and adds:
27
+
28
+ - tag requests addressed by symbol instance ID, including UDT members and program-scoped tags
29
+ - tag-table change detection with the controller's edit counter, so online edits trigger a
30
+ reload instead of reading a reused instance ID
31
+ - fast reloads, batched tag-list uploads and a restart cache (``tag_cache_file``)
32
+ - Add-On Instruction access probing: whole AOI reads return their readable parameters, and
33
+ AOI locals are refused without a request
34
+
35
+ Without the work of the pycomm3 developers, and of the original `pycomm`_ developers before
36
+ them, ``ciplink`` would not exist. The pycomm3 `documentation`_ applies to ciplink apart from
37
+ the import name.
38
+
39
+
40
+ .. _pycomm3: https://github.com/ottowayi/pycomm3
41
+
42
+ .. _pycomm: https://github.com/ruscito/pycomm
43
+
44
+ .. _documentation: https://docs.pycomm3.dev
45
+
46
+
47
+ Drivers
48
+ =======
49
+
50
+ ``ciplink`` includes 3 drivers:
51
+
52
+ - `CIPDriver`_
53
+ This driver is the base driver for the library, it handles common CIP services used
54
+ by the other drivers. Things like opening/closing a connection, register/unregister sessions,
55
+ forward open/close services, device discovery, and generic messaging. It can be used to connect to
56
+ any Ethernet/IP device, like: drives, switches, meters, and other non-PLC devices.
57
+ ``ciplink`` is a client for explicit messaging only (unconnected/UCMM and class 3 connected over TCP 44818);
58
+ implicit class 0/1 I/O (UDP 2222), produced/consumed tags and acting as a CIP server/adapter are not supported.
59
+
60
+ - `LogixDriver`_
61
+ This driver supports services specific to ControlLogix, CompactLogix, and Micro800 PLCs.
62
+ Services like reading/writing tags, uploading the tag list, and getting/setting the PLC time.
63
+
64
+ - `SLCDriver`_
65
+ This driver supports basic reading/writing data files in a SLC500 or MicroLogix PLCs. It is
66
+ a port of the ``SlcDriver`` from ``pycomm`` with minimal changes to make the API similar to the
67
+ other drivers. Currently this driver is considered legacy and it's development will be on
68
+ a limited basis. PLC-5 processors are not supported by any driver.
69
+
70
+ .. _CIPDriver: https://docs.pycomm3.dev/en/latest/usage/cipdriver.html
71
+
72
+ .. _LogixDriver: https://docs.pycomm3.dev/en/latest/usage/logixdriver.html
73
+
74
+ .. _SLCDriver: https://docs.pycomm3.dev/en/latest/usage/slcdriver.html
75
+
76
+ Disclaimer
77
+ ==========
78
+
79
+ PLCs can be used to control heavy or dangerous equipment, this library is provided "as is" and makes no guarantees on
80
+ its reliability in a production environment. This library makes no promises in the completeness or correctness of the
81
+ protocol implementations and should not be solely relied upon for critical systems. The development for this library
82
+ is aimed at providing quick and convenient access for reading/writing data inside Allen-Bradley PLCs.
83
+
84
+
85
+ Setup
86
+ =====
87
+
88
+ The package can be installed from `PyPI`_ using ``pip``: ``pip install ciplink`` or ``python -m pip install ciplink``.
89
+
90
+ .. _PyPI: https://pypi.org/project/ciplink/
91
+
92
+ Optionally, you may configure logging using the Python standard `logging`_ library. A convenience method is provided
93
+ to help configure basic logging, see the `Logging Section`_ in the docs for more information.
94
+
95
+ .. _logging: https://docs.python.org/3/library/logging.html
96
+
97
+ .. _Logging Section: https://docs.pycomm3.dev/en/latest/getting_started.html#logging
98
+
99
+
100
+ Python and OS Support
101
+ =====================
102
+
103
+ ``ciplink`` is a Python 3-only library and is supported on Python 3.9 and newer.
104
+ There should be no OS-specific requirements and should be able to run on any OS that Python is supported on.
105
+ Development and testing is done primarily on Windows 10. If you encounter an OS-related problem, please open an issue
106
+ in the `GitHub repository`_ and it will be investigated.
107
+
108
+ .. attention::
109
+
110
+ Python 3.6.0 is not supported due to ``NamedTuple`` not supporting
111
+ `default values and methods <https://docs.python.org/3/library/typing.html#typing.NamedTuple>`_ until 3.6.1
112
+
113
+ .. _GitHub repository: https://github.com/ottowayi/pycomm3
114
+
115
+ .. <<end>>
116
+
117
+ Documentation
118
+ =============
119
+
120
+ This README covers a basic overview of the library, full documentation can be found on
121
+ `Read the Docs`_ or by visiting `https://pycomm3.dev <https://pycomm3.dev>`_.
122
+
123
+ .. _Read the Docs: https://pycomm3.readthedocs.io/en/latest/
124
+
125
+ Contributions
126
+ =============
127
+
128
+ If you'd like to contribute or are having an issue, please read the `Contributing`_ guidelines.
129
+
130
+ .. _Contributing: CONTRIBUTING.md
131
+
132
+
133
+ Highlighted Features
134
+ ====================
135
+
136
+ - ``generic_message`` for extra functionality not directly implemented
137
+ - working similar to the MSG instruction in Logix, arguments similar to the MESSAGE properties
138
+ - See the examples section for things like getting/setting drive parameters, IP configuration, or uploading an EDS file
139
+ - used internally to implement some of the other methods (get/set_plc_time, forward open/close, etc)
140
+ - simplified data types
141
+ - allows use of standard Python types by abstracting CIP implementation details away from the user
142
+ - strings use normal Python ``str`` objects, does not require handling of the ``LEN`` and ``DATA`` attributes separately
143
+ - custom string types are also identified automatically and not limited to just the builtin one
144
+ - BOOL arrays use normal Python ``bool`` objects, does not require complicated bit shifting of the DWORD value
145
+ - powerful type system to allow types to represent any CIP object and handle encoding/decoding the object
146
+
147
+ LogixDriver
148
+ -----------
149
+
150
+ - simple API, only 1 ``read`` method and 1 ``write`` method for tags.
151
+ - does not require using different methods for different data types
152
+ - requires the tag name only, no other information required from the user
153
+ - automatically manages request/response size to pack as many requests into a single packet
154
+ - automatically handles fragmented requests for large tags that can't fit in a single packet
155
+ - both support full structure reading/writing (UDTs, AOIs, etc)
156
+ - for ``read`` the ``Tag.value`` will be a ``dict`` of ``{attribute: value}``
157
+ - for ``write`` the value should be a dict of ``{attribute: value}`` , nesting as needed
158
+ - does not do partial writes, the value must match the complete structure
159
+ - not recommended for builtin type (TIMER, CONTROL, COUNTER, etc)
160
+ - both require no attributes to have an External Access of None
161
+ - uploads the tag list and data type definitions from the PLC
162
+ - no requirement for user to determine tags available (like from an L5X export)
163
+ - definitions are required for ``read``/``write`` methods
164
+ - automatically enables/disables different features based on the target PLC
165
+ - Extended Forward Open (EN2T or newer and v20+)
166
+ - Symbol Instance Addressing (Logix v21+)
167
+ - detection of Micro800 and disables unsupported features (CIP Path, Ex. Forward Open, Instance Addressing, etc)
168
+
169
+ LogixDriver Overview
170
+ ====================
171
+
172
+ Creating a driver is simple, only a ``path`` argument is required. The ``path`` can be the IP address, IP and slot,
173
+ or a full CIP route, refer to the documentation for more details. The example below shows how to create a simple
174
+ driver and print some of the information collected about the device.
175
+
176
+ ::
177
+
178
+ from ciplink import LogixDriver
179
+
180
+ with LogixDriver('10.20.30.100/1') as plc:
181
+ print(plc)
182
+ # OUTPUT:
183
+ # Program Name: PLCA, Device: 1756-L83E/B, Revision: 28.13
184
+
185
+ print(plc.info)
186
+ # OUTPUT:
187
+ # {'vendor': 'Rockwell Automation/Allen-Bradley', 'product_type': 'Programmable Logic Controller',
188
+ # 'product_code': 166, 'revision': {'major': 28, 'minor': 13}, 'status': b'`0', 'serial': 'ffffffff',
189
+ # 'product_name': '1756-L83E/B', 'keyswitch': 'REMOTE RUN', 'name': 'PLCA'}
190
+
191
+
192
+ Reading/Writing Tags
193
+ --------------------
194
+
195
+ Reading or writing tags is as simple as calling the ``read`` and ``write`` methods. Both methods accept any number of tags,
196
+ and will automatically pack multiple tags into a *Multiple Service Packet Service (0x0A)* while making sure to stay below the connection size.
197
+ If there is a tag value that cannot fit within the request/reply packet, it will automatically handle that tag independently
198
+ using the *Read Tag Fragmented (0x52)* or *Write Tag Fragmented (0x53)* requests.
199
+
200
+ Both methods will return ``Tag`` objects to reflect the success or failure of the operation.
201
+
202
+ ::
203
+
204
+ class Tag(NamedTuple):
205
+ tag: str # the name of the tag, does not include ``{<# elements>}`` from request
206
+ value: Any # value read or written, may be ``None`` if an error occurred
207
+ type: Optional[str] = None # data type of tag, including ``[<# elements>]`` from request
208
+ error: Optional[str] = None # ``None`` if successful, else the CIP error or exception thrown
209
+
210
+ ``Tag`` objects are considered successful (truthy) if the ``value`` is not ``None`` and the ``error`` is ``None``.
211
+
212
+
213
+ Examples::
214
+
215
+ with LogixDriver('10.20.30.100') as plc:
216
+ plc.read('tag1', 'tag2', 'tag3') # read multiple tags
217
+ plc.read('array{10}') # read 10 elements starting at 0 from an array
218
+ plc.read('array[5]{20}') # read 20 elements starting at elements 5 from an array
219
+ plc.read('string_tag') # read a string tag and get a string
220
+ plc.read('a_udt_tag') # the response .value will be a dict like: {'attr1`: 1, 'attr2': 'a string', ...}
221
+
222
+ # writes require a sequence of tuples of [(tag name, value), ... ]
223
+ plc.write('tag1', 0) # single writes do not need to be passed as a tuple
224
+ plc.write(('tag1', 0), ('tag2', 1), ('tag3', 2)) # write multiple tags
225
+ plc.write(('array{5}', [1, 2, 3, 4, 5])) # write 5 elements to an array starting at the 0 element
226
+ plc.write('array[10]{5}', [1, 2, 3, 4, 5]) # write 5 elements to an array starting at element 10
227
+ plc.write('string_tag', 'Hello World!') # write to a string tag with a string
228
+ plc.write('string_array[2]{5}', 'Write an array of strings'.split()) # write an array of 5 strings starting at element 2
229
+ plc.write('a_udt_tag', {'attr1': 1, 'attr2': 'a string', ...}) # can also use a dict to write a struct
230
+
231
+ # Check the results
232
+ results = plc.read('tag1', 'tag2', 'tag3')
233
+ if all(results):
234
+ print('They all worked!')
235
+ else:
236
+ for result in results:
237
+ if not result:
238
+ print(f'Reading tag {result.tag} failed with error: {result.error}')
239
+
240
+ .. Note::
241
+
242
+ Tag names for both ``read`` and ``write`` are matched without regard to case (an exact match is tried first).
243
+
244
+
245
+ Unit Testing
246
+ ============
247
+
248
+ ``pytest`` is used for unit testing. The ``tests`` directory contains an L5X export of the testing program
249
+ that contains all tags necessary for testing. The only requirement for testing (besides a running PLC with the testing
250
+ program) is the environment variable ``PLCPATH`` for the PLC defined.
251
+
252
+ User Tests
253
+ ----------
254
+
255
+ These tests are for users to run. There are a few tests that are specific to a demo
256
+ plc, those are excluded. To run them you have the following options:
257
+
258
+ with `tox`:
259
+
260
+ - modify the ``PLCPATH`` variable in ``tox.ini``
261
+ - then run this command: ``tox -e user``
262
+
263
+ or with ``pytest``:
264
+
265
+ .. code-block::
266
+
267
+ set PLCPATH=192.168.1.100
268
+ pytest --ignore tests/online/test_demo_plc.py
269
+
270
+ *(or the equivalent in your shell)*
271
+
272
+
273
+ .. Note::
274
+ Test coverage is not complete, pull requests are welcome to help improve coverage.
275
+
276
+
277
+ License
278
+ =======
279
+ ``ciplink`` is distributed under the MIT License
@@ -0,0 +1,40 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (c) 2021 Ian Ottoway <ian@ottoway.dev>
4
+ # Copyright (c) 2014 Agostino Ruscito <ruscito@gmail.com>
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+ #
24
+
25
+ from ._version import __version__, __version_info__
26
+ from .logger import *
27
+ from .const import *
28
+ from .tag import Tag
29
+ from .exceptions import *
30
+ from .cip import *
31
+ from .custom_types import *
32
+ from .cip_driver import *
33
+ from .logix_auth import LogixMetadataCredentials
34
+ from .logix_driver import *
35
+ from .slc_driver import *
36
+
37
+ from types import ModuleType as _ModuleType
38
+
39
+ # export everything above except the submodules, so `from ciplink import *` does not shadow map, logger, etc.
40
+ __all__ = [n for n, v in list(globals().items()) if not n.startswith('_') and not isinstance(v, _ModuleType)]