profinet-py 0.6.0__tar.gz → 0.6.2__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.
- profinet_py-0.6.2/LICENSE-COMMERCIAL.md +28 -0
- {profinet_py-0.6.0/profinet_py.egg-info → profinet_py-0.6.2}/PKG-INFO +25 -42
- {profinet_py-0.6.0 → profinet_py-0.6.2}/README.md +15 -33
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/__init__.py +2 -6
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/alarm_listener.py +2 -2
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/cli.py +38 -2
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/dcp.py +8 -11
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/gsdml.py +2 -2
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/protocol.py +0 -3
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/rpc.py +0 -3
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/util.py +13 -312
- {profinet_py-0.6.0 → profinet_py-0.6.2/profinet_py.egg-info}/PKG-INFO +25 -42
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet_py.egg-info/SOURCES.txt +2 -0
- profinet_py-0.6.2/profinet_py.egg-info/requires.txt +9 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/pyproject.toml +20 -20
- profinet_py-0.6.2/tests/test_cyclic_hw.py +466 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/tests/test_rpc.py +109 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/tests/test_util.py +64 -0
- profinet_py-0.6.0/profinet_py.egg-info/requires.txt +0 -8
- {profinet_py-0.6.0 → profinet_py-0.6.2}/LICENSE +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/alarms.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/blocks.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/cyclic.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/device.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/diagnosis.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/exceptions.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/indices.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/py.typed +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/rt.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet/vendors.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet_py.egg-info/dependency_links.txt +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet_py.egg-info/entry_points.txt +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/profinet_py.egg-info/top_level.txt +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/setup.cfg +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/tests/test_alarm_listener.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/tests/test_blocks.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/tests/test_cyclic.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/tests/test_dcp.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/tests/test_diagnosis.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/tests/test_exceptions.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/tests/test_gsdml.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/tests/test_indices.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/tests/test_protocol.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/tests/test_review_fixes.py +0 -0
- {profinet_py-0.6.0 → profinet_py-0.6.2}/tests/test_rt.py +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Commercial License — profinet-py
|
|
2
|
+
|
|
3
|
+
This software is dual-licensed:
|
|
4
|
+
|
|
5
|
+
- **Open source** under the [GNU General Public License v3.0](LICENSE) for free and
|
|
6
|
+
open source use.
|
|
7
|
+
- **Commercial/OEM license** for use in proprietary products, described below.
|
|
8
|
+
|
|
9
|
+
## When you need a commercial license
|
|
10
|
+
|
|
11
|
+
A commercial license is required if you want to:
|
|
12
|
+
|
|
13
|
+
- Embed profinet-py in proprietary software or hardware products
|
|
14
|
+
- Distribute profinet-py as part of a closed-source application
|
|
15
|
+
- Use profinet-py in a product without complying with GPL-3.0 obligations
|
|
16
|
+
(source disclosure, copyleft, license notices)
|
|
17
|
+
|
|
18
|
+
## What the commercial license provides
|
|
19
|
+
|
|
20
|
+
- Permission to use, modify, and distribute profinet-py in proprietary products
|
|
21
|
+
- No requirement to disclose your source code
|
|
22
|
+
- No copyleft obligations
|
|
23
|
+
- Priority support options available
|
|
24
|
+
|
|
25
|
+
## Contact
|
|
26
|
+
|
|
27
|
+
For commercial licensing inquiries, contact the maintainer via the details listed
|
|
28
|
+
at <https://github.com/f0rw4rd>.
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: profinet-py
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: PROFINET IO-Controller library for Python
|
|
5
5
|
Author: f0rw4rd
|
|
6
6
|
Maintainer: f0rw4rd
|
|
7
7
|
License-Expression: GPL-3.0-only
|
|
8
|
-
Project-URL: Homepage, https://github.com/f0rw4rd/profinet
|
|
9
|
-
Project-URL: Repository, https://github.com/f0rw4rd/profinet
|
|
10
|
-
Project-URL:
|
|
11
|
-
Project-URL: Issues, https://github.com/f0rw4rd/profinet/issues
|
|
8
|
+
Project-URL: Homepage, https://github.com/f0rw4rd/profinet-py
|
|
9
|
+
Project-URL: Repository, https://github.com/f0rw4rd/profinet-py
|
|
10
|
+
Project-URL: Issues, https://github.com/f0rw4rd/profinet-py/issues
|
|
12
11
|
Keywords: profinet,industrial,ics,ot,plc,automation,siemens,dcp,rpc
|
|
13
12
|
Classifier: Development Status :: 4 - Beta
|
|
14
13
|
Classifier: Environment :: Console
|
|
@@ -23,17 +22,26 @@ Classifier: Typing :: Typed
|
|
|
23
22
|
Requires-Python: >=3.10
|
|
24
23
|
Description-Content-Type: text/markdown
|
|
25
24
|
License-File: LICENSE
|
|
25
|
+
License-File: LICENSE-COMMERCIAL.md
|
|
26
26
|
Requires-Dist: construct>=2.10
|
|
27
27
|
Provides-Extra: dev
|
|
28
|
-
Requires-Dist: pytest>=
|
|
28
|
+
Requires-Dist: pytest>=9.0.0; extra == "dev"
|
|
29
29
|
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist:
|
|
32
|
-
Requires-Dist:
|
|
30
|
+
Requires-Dist: ruff>=0.15.0; extra == "dev"
|
|
31
|
+
Requires-Dist: vulture>=2.14; extra == "dev"
|
|
32
|
+
Requires-Dist: bandit>=1.9.3; extra == "dev"
|
|
33
|
+
Requires-Dist: pre-commit>=4.0.0; extra == "dev"
|
|
33
34
|
Dynamic: license-file
|
|
34
35
|
|
|
35
36
|
# profinet-py
|
|
36
37
|
|
|
38
|
+
[](https://github.com/f0rw4rd/profinet-py/actions/workflows/ci.yml)
|
|
39
|
+
[](https://pypi.org/project/profinet-py/)
|
|
40
|
+
[](https://pypi.org/project/profinet-py/)
|
|
41
|
+
[](LICENSE)
|
|
42
|
+
[](https://github.com/astral-sh/ruff)
|
|
43
|
+
[](CONTRIBUTING.md)
|
|
44
|
+
|
|
37
45
|
A Python library for PROFINET IO communication, acting as an IO-Controller.
|
|
38
46
|
|
|
39
47
|
## Features
|
|
@@ -46,7 +54,7 @@ A Python library for PROFINET IO communication, acting as an IO-Controller.
|
|
|
46
54
|
- **Diagnosis Parsing**: Channel, extended channel, and qualified channel diagnosis decoding
|
|
47
55
|
- **Vendor Registry**: 2100+ PROFINET vendor IDs with name lookup
|
|
48
56
|
- **Declarative Parsing**: Binary protocol parsing via [construct](https://construct.readthedocs.io/) library
|
|
49
|
-
- **Cross-Platform**: Linux (AF_PACKET), Windows (Npcap)
|
|
57
|
+
- **Cross-Platform**: Linux (AF_PACKET), Windows (Npcap)
|
|
50
58
|
- **High-level API**: `ProfinetDevice` class and `scan()` for quick device interaction
|
|
51
59
|
|
|
52
60
|
## Requirements
|
|
@@ -61,7 +69,6 @@ A Python library for PROFINET IO communication, acting as an IO-Controller.
|
|
|
61
69
|
|----------|-------------------|----------------|
|
|
62
70
|
| **Linux** | AF_PACKET (built-in) | None |
|
|
63
71
|
| **Windows** | Npcap (wpcap.dll) | Install [Npcap](https://npcap.com/) with "WinPcap API-compatible Mode" enabled |
|
|
64
|
-
| **macOS** | libpcap (built-in) | None |
|
|
65
72
|
|
|
66
73
|
## Installation
|
|
67
74
|
|
|
@@ -72,8 +79,8 @@ pip install profinet-py
|
|
|
72
79
|
From source:
|
|
73
80
|
|
|
74
81
|
```bash
|
|
75
|
-
git clone https://github.com/f0rw4rd/profinet.git
|
|
76
|
-
cd profinet
|
|
82
|
+
git clone https://github.com/f0rw4rd/profinet-py.git
|
|
83
|
+
cd profinet-py
|
|
77
84
|
pip install -e ".[dev]"
|
|
78
85
|
```
|
|
79
86
|
|
|
@@ -131,34 +138,6 @@ profinet -i eth0 cyclic device-name --gsdml device.xml
|
|
|
131
138
|
profinet -i eth0 cyclic device-name --gsdml device.xml --cycle-ms 16
|
|
132
139
|
```
|
|
133
140
|
|
|
134
|
-
## Credits
|
|
135
|
-
|
|
136
|
-
This project is a modernized fork of the original PROFINET library by **Alfred Krohmer**:
|
|
137
|
-
|
|
138
|
-
- **Original Repository**: https://github.com/alfredkrohmer/profinet
|
|
139
|
-
- **Original Author**: Alfred Krohmer (2015)
|
|
140
|
-
|
|
141
|
-
### Changes in this fork
|
|
142
|
-
|
|
143
|
-
- Cross-platform support (Windows via Npcap ctypes, macOS via libpcap ctypes)
|
|
144
|
-
- Dropped Python 3.8/3.9 (EOL), targets Python 3.10+
|
|
145
|
-
- Migrated all binary parsing from `struct` to `construct` library for declarative, readable protocol definitions
|
|
146
|
-
- Fixed 5 protocol bugs verified against Wireshark's PROFINET dissector and IEC 61158-6-10:
|
|
147
|
-
- DCP Option 0x04 is Reserved (not LLDP)
|
|
148
|
-
- Block Type 0x0012 is ExpectedIdentificationData (not QualifiedChannelDiagnosis)
|
|
149
|
-
- RT_CLASS_1 frame ID range starts at 0x8000 (not 0xC000)
|
|
150
|
-
- Device Suboption 0x09 does not exist in the spec
|
|
151
|
-
- DCP Identify uses separate Frame IDs for request (0xFEFE) and response (0xFEFF)
|
|
152
|
-
- DCP SET operations now validate response block error codes instead of silently succeeding
|
|
153
|
-
- Diagnosis parsing module with channel/extended/qualified channel support
|
|
154
|
-
- Alarm notification parsing and background alarm listener
|
|
155
|
-
- Cyclic I/O controller for RT_CLASS_1 periodic data exchange with explicit state machine (`IDLE → RUNNING → STOPPING → STOPPED`, `FAULT`), double-buffered output data, per-frame cycle counter tracking (gap/duplicate/out-of-order detection), watchdog with automatic fault transition, IOCS consumer status handling, graceful stop (RUN→STOP frames), and separate TX/RX sockets
|
|
156
|
-
- High-level `ProfinetDevice` API with `start_cyclic()` for integrated cyclic IO, plus `scan()`/`scan_dict()` convenience functions
|
|
157
|
-
- 2100+ vendor ID registry with lookup
|
|
158
|
-
- CLI tool for discovery, I&M reading, and raw record access
|
|
159
|
-
- 700+ unit tests, Docker-based integration tests against p-net device emulator
|
|
160
|
-
- Type hints, ruff linting, mypy checking
|
|
161
|
-
|
|
162
141
|
## Support
|
|
163
142
|
|
|
164
143
|
If you find this project useful, consider supporting development:
|
|
@@ -167,7 +146,11 @@ If you find this project useful, consider supporting development:
|
|
|
167
146
|
|
|
168
147
|
## License
|
|
169
148
|
|
|
170
|
-
|
|
149
|
+
Dual-licensed under [GPL-3.0](LICENSE) and a [commercial license](LICENSE-COMMERCIAL.md).
|
|
150
|
+
|
|
151
|
+
Free for open source use under GPL-3.0. If you want to use profinet-py in proprietary
|
|
152
|
+
products without GPL obligations, a commercial license is available —
|
|
153
|
+
see [LICENSE-COMMERCIAL.md](LICENSE-COMMERCIAL.md) for details.
|
|
171
154
|
|
|
172
155
|
## References
|
|
173
156
|
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# profinet-py
|
|
2
2
|
|
|
3
|
+
[](https://github.com/f0rw4rd/profinet-py/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/profinet-py/)
|
|
5
|
+
[](https://pypi.org/project/profinet-py/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://github.com/astral-sh/ruff)
|
|
8
|
+
[](CONTRIBUTING.md)
|
|
9
|
+
|
|
3
10
|
A Python library for PROFINET IO communication, acting as an IO-Controller.
|
|
4
11
|
|
|
5
12
|
## Features
|
|
@@ -12,7 +19,7 @@ A Python library for PROFINET IO communication, acting as an IO-Controller.
|
|
|
12
19
|
- **Diagnosis Parsing**: Channel, extended channel, and qualified channel diagnosis decoding
|
|
13
20
|
- **Vendor Registry**: 2100+ PROFINET vendor IDs with name lookup
|
|
14
21
|
- **Declarative Parsing**: Binary protocol parsing via [construct](https://construct.readthedocs.io/) library
|
|
15
|
-
- **Cross-Platform**: Linux (AF_PACKET), Windows (Npcap)
|
|
22
|
+
- **Cross-Platform**: Linux (AF_PACKET), Windows (Npcap)
|
|
16
23
|
- **High-level API**: `ProfinetDevice` class and `scan()` for quick device interaction
|
|
17
24
|
|
|
18
25
|
## Requirements
|
|
@@ -27,7 +34,6 @@ A Python library for PROFINET IO communication, acting as an IO-Controller.
|
|
|
27
34
|
|----------|-------------------|----------------|
|
|
28
35
|
| **Linux** | AF_PACKET (built-in) | None |
|
|
29
36
|
| **Windows** | Npcap (wpcap.dll) | Install [Npcap](https://npcap.com/) with "WinPcap API-compatible Mode" enabled |
|
|
30
|
-
| **macOS** | libpcap (built-in) | None |
|
|
31
37
|
|
|
32
38
|
## Installation
|
|
33
39
|
|
|
@@ -38,8 +44,8 @@ pip install profinet-py
|
|
|
38
44
|
From source:
|
|
39
45
|
|
|
40
46
|
```bash
|
|
41
|
-
git clone https://github.com/f0rw4rd/profinet.git
|
|
42
|
-
cd profinet
|
|
47
|
+
git clone https://github.com/f0rw4rd/profinet-py.git
|
|
48
|
+
cd profinet-py
|
|
43
49
|
pip install -e ".[dev]"
|
|
44
50
|
```
|
|
45
51
|
|
|
@@ -97,34 +103,6 @@ profinet -i eth0 cyclic device-name --gsdml device.xml
|
|
|
97
103
|
profinet -i eth0 cyclic device-name --gsdml device.xml --cycle-ms 16
|
|
98
104
|
```
|
|
99
105
|
|
|
100
|
-
## Credits
|
|
101
|
-
|
|
102
|
-
This project is a modernized fork of the original PROFINET library by **Alfred Krohmer**:
|
|
103
|
-
|
|
104
|
-
- **Original Repository**: https://github.com/alfredkrohmer/profinet
|
|
105
|
-
- **Original Author**: Alfred Krohmer (2015)
|
|
106
|
-
|
|
107
|
-
### Changes in this fork
|
|
108
|
-
|
|
109
|
-
- Cross-platform support (Windows via Npcap ctypes, macOS via libpcap ctypes)
|
|
110
|
-
- Dropped Python 3.8/3.9 (EOL), targets Python 3.10+
|
|
111
|
-
- Migrated all binary parsing from `struct` to `construct` library for declarative, readable protocol definitions
|
|
112
|
-
- Fixed 5 protocol bugs verified against Wireshark's PROFINET dissector and IEC 61158-6-10:
|
|
113
|
-
- DCP Option 0x04 is Reserved (not LLDP)
|
|
114
|
-
- Block Type 0x0012 is ExpectedIdentificationData (not QualifiedChannelDiagnosis)
|
|
115
|
-
- RT_CLASS_1 frame ID range starts at 0x8000 (not 0xC000)
|
|
116
|
-
- Device Suboption 0x09 does not exist in the spec
|
|
117
|
-
- DCP Identify uses separate Frame IDs for request (0xFEFE) and response (0xFEFF)
|
|
118
|
-
- DCP SET operations now validate response block error codes instead of silently succeeding
|
|
119
|
-
- Diagnosis parsing module with channel/extended/qualified channel support
|
|
120
|
-
- Alarm notification parsing and background alarm listener
|
|
121
|
-
- Cyclic I/O controller for RT_CLASS_1 periodic data exchange with explicit state machine (`IDLE → RUNNING → STOPPING → STOPPED`, `FAULT`), double-buffered output data, per-frame cycle counter tracking (gap/duplicate/out-of-order detection), watchdog with automatic fault transition, IOCS consumer status handling, graceful stop (RUN→STOP frames), and separate TX/RX sockets
|
|
122
|
-
- High-level `ProfinetDevice` API with `start_cyclic()` for integrated cyclic IO, plus `scan()`/`scan_dict()` convenience functions
|
|
123
|
-
- 2100+ vendor ID registry with lookup
|
|
124
|
-
- CLI tool for discovery, I&M reading, and raw record access
|
|
125
|
-
- 700+ unit tests, Docker-based integration tests against p-net device emulator
|
|
126
|
-
- Type hints, ruff linting, mypy checking
|
|
127
|
-
|
|
128
106
|
## Support
|
|
129
107
|
|
|
130
108
|
If you find this project useful, consider supporting development:
|
|
@@ -133,7 +111,11 @@ If you find this project useful, consider supporting development:
|
|
|
133
111
|
|
|
134
112
|
## License
|
|
135
113
|
|
|
136
|
-
|
|
114
|
+
Dual-licensed under [GPL-3.0](LICENSE) and a [commercial license](LICENSE-COMMERCIAL.md).
|
|
115
|
+
|
|
116
|
+
Free for open source use under GPL-3.0. If you want to use profinet-py in proprietary
|
|
117
|
+
products without GPL obligations, a commercial license is available —
|
|
118
|
+
see [LICENSE-COMMERCIAL.md](LICENSE-COMMERCIAL.md) for details.
|
|
137
119
|
|
|
138
120
|
## References
|
|
139
121
|
|
|
@@ -9,12 +9,8 @@ A Python library for PROFINET IO communication, supporting:
|
|
|
9
9
|
This library acts as an IO-Controller, allowing connection to and
|
|
10
10
|
communication with PROFINET IO-Devices.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
https://github.com/alfredkrohmer/profinet
|
|
15
|
-
|
|
16
|
-
Modernized for Python 3.8+ by f0rw4rd (2024)
|
|
17
|
-
https://github.com/f0rw4rd/profinet-py
|
|
12
|
+
Author: f0rw4rd
|
|
13
|
+
https://github.com/f0rw4rd/profinet-py
|
|
18
14
|
"""
|
|
19
15
|
|
|
20
16
|
from . import blocks, indices
|
|
@@ -194,7 +194,7 @@ class AlarmListener:
|
|
|
194
194
|
Returns:
|
|
195
195
|
Configured socket for alarm reception.
|
|
196
196
|
For Layer 2: platform-abstracted raw socket (AF_PACKET on Linux,
|
|
197
|
-
NpcapSocket on Windows
|
|
197
|
+
NpcapSocket on Windows).
|
|
198
198
|
For UDP: standard socket.
|
|
199
199
|
|
|
200
200
|
Raises:
|
|
@@ -210,7 +210,7 @@ class AlarmListener:
|
|
|
210
210
|
# UDP socket (port 34964) — works on all platforms
|
|
211
211
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
212
212
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
213
|
-
sock.bind(("0.0.0.0", 34964))
|
|
213
|
+
sock.bind(("0.0.0.0", 34964)) # nosec B104 — must receive from any interface
|
|
214
214
|
|
|
215
215
|
# Non-blocking timeout for clean shutdown
|
|
216
216
|
sock.settimeout(1.0)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
PROFINET command-line interface.
|
|
3
3
|
|
|
4
|
-
Credits: Original implementation by Alfred Krohmer (2015)
|
|
5
4
|
"""
|
|
6
5
|
|
|
7
6
|
from __future__ import annotations
|
|
@@ -141,6 +140,31 @@ def cmd_read(args: argparse.Namespace) -> int:
|
|
|
141
140
|
sock.close()
|
|
142
141
|
|
|
143
142
|
|
|
143
|
+
def cmd_write(args: argparse.Namespace) -> int:
|
|
144
|
+
"""Execute write command."""
|
|
145
|
+
sock = ethernet_socket(args.interface, 3)
|
|
146
|
+
try:
|
|
147
|
+
src = get_mac(args.interface)
|
|
148
|
+
|
|
149
|
+
print(f"Connecting to {args.target}...")
|
|
150
|
+
info = rpc.get_station_info(sock, src, args.target)
|
|
151
|
+
|
|
152
|
+
with rpc.RPCCon(info) as conn:
|
|
153
|
+
conn.connect(src)
|
|
154
|
+
|
|
155
|
+
idx = int(args.index, 16) if args.index.startswith("0x") else int(args.index)
|
|
156
|
+
data = bytes.fromhex(args.data.replace(" ", ""))
|
|
157
|
+
conn.write(api=args.api, slot=args.slot, subslot=args.subslot, idx=idx, data=data)
|
|
158
|
+
|
|
159
|
+
print(
|
|
160
|
+
f"Wrote {len(data)} bytes to slot={args.slot} subslot={args.subslot} index=0x{idx:04X}"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
return 0
|
|
164
|
+
finally:
|
|
165
|
+
sock.close()
|
|
166
|
+
|
|
167
|
+
|
|
144
168
|
def cmd_read_inm0_filter(args: argparse.Namespace) -> int:
|
|
145
169
|
"""Execute read-inm0-filter command."""
|
|
146
170
|
sock = ethernet_socket(args.interface, 3)
|
|
@@ -574,7 +598,7 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
574
598
|
parser = argparse.ArgumentParser(
|
|
575
599
|
prog="profinet",
|
|
576
600
|
description="PROFINET IO-Controller CLI",
|
|
577
|
-
epilog="
|
|
601
|
+
epilog="https://github.com/f0rw4rd/profinet-py",
|
|
578
602
|
)
|
|
579
603
|
|
|
580
604
|
parser.add_argument(
|
|
@@ -631,6 +655,18 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
631
655
|
sub.add_argument("--index", required=True, help="Record index (hex with 0x prefix)")
|
|
632
656
|
sub.set_defaults(func=cmd_read)
|
|
633
657
|
|
|
658
|
+
# write
|
|
659
|
+
sub = subparsers.add_parser("write", help="Write data record")
|
|
660
|
+
sub.add_argument("target", metavar="NAME", help="Station name (e.g. my-device)")
|
|
661
|
+
sub.add_argument("--api", type=int, default=0, help="API (default: 0)")
|
|
662
|
+
sub.add_argument("--slot", type=int, required=True, help="Slot number")
|
|
663
|
+
sub.add_argument("--subslot", type=int, required=True, help="Subslot number")
|
|
664
|
+
sub.add_argument(
|
|
665
|
+
"--index", required=True, help="Record index (hex with 0x prefix, e.g. 0xAFF1)"
|
|
666
|
+
)
|
|
667
|
+
sub.add_argument("data", metavar="HEX", help="Data to write as hex string (e.g. deadbeef)")
|
|
668
|
+
sub.set_defaults(func=cmd_write)
|
|
669
|
+
|
|
634
670
|
# read-inm0-filter
|
|
635
671
|
sub = subparsers.add_parser("read-inm0-filter", help="Read device topology")
|
|
636
672
|
sub.add_argument("target", metavar="NAME", help="Station name (e.g. my-device)")
|
|
@@ -7,15 +7,12 @@ Provides device discovery and basic configuration operations:
|
|
|
7
7
|
- get_param(): Read device parameter (name, IP)
|
|
8
8
|
- set_param(): Write device parameter
|
|
9
9
|
|
|
10
|
-
Credits:
|
|
11
|
-
Original implementation by Alfred Krohmer (2015)
|
|
12
|
-
https://github.com/alfredkrohmer/profinet
|
|
13
10
|
"""
|
|
14
11
|
|
|
15
12
|
from __future__ import annotations
|
|
16
13
|
|
|
17
14
|
import logging
|
|
18
|
-
import
|
|
15
|
+
import secrets
|
|
19
16
|
import time
|
|
20
17
|
from collections.abc import Callable
|
|
21
18
|
from dataclasses import dataclass
|
|
@@ -507,7 +504,7 @@ PARAMS: Dict[str, Tuple[int, int]] = {
|
|
|
507
504
|
|
|
508
505
|
def _generate_xid() -> int:
|
|
509
506
|
"""Generate random transaction ID for DCP requests."""
|
|
510
|
-
return
|
|
507
|
+
return secrets.randbelow(0x100000000)
|
|
511
508
|
|
|
512
509
|
|
|
513
510
|
# =============================================================================
|
|
@@ -587,9 +584,9 @@ class DCPDeviceDescription:
|
|
|
587
584
|
self.netmask = s2ip(ip_block[6:10])
|
|
588
585
|
self.gateway = s2ip(ip_block[10:14])
|
|
589
586
|
else:
|
|
590
|
-
self.ip = "0.0.0.0"
|
|
591
|
-
self.netmask = "0.0.0.0"
|
|
592
|
-
self.gateway = "0.0.0.0"
|
|
587
|
+
self.ip = "0.0.0.0" # nosec B104 — PROFINET default (no IP assigned)
|
|
588
|
+
self.netmask = "0.0.0.0" # nosec B104
|
|
589
|
+
self.gateway = "0.0.0.0" # nosec B104
|
|
593
590
|
logger.warning(f"Device {self.mac} has no IP configuration")
|
|
594
591
|
|
|
595
592
|
# Handle device ID (optional)
|
|
@@ -1335,9 +1332,9 @@ def send_hello(
|
|
|
1335
1332
|
sock: socket,
|
|
1336
1333
|
src: bytes,
|
|
1337
1334
|
station_name: str,
|
|
1338
|
-
ip: str = "0.0.0.0",
|
|
1339
|
-
netmask: str = "0.0.0.0",
|
|
1340
|
-
gateway: str = "0.0.0.0",
|
|
1335
|
+
ip: str = "0.0.0.0", # nosec B104 — PROFINET default (no IP assigned)
|
|
1336
|
+
netmask: str = "0.0.0.0", # nosec B104
|
|
1337
|
+
gateway: str = "0.0.0.0", # nosec B104
|
|
1341
1338
|
device_id: Tuple[int, int] = (0, 0),
|
|
1342
1339
|
device_role: int = DEVICE_ROLE_IO_DEVICE,
|
|
1343
1340
|
) -> None:
|
|
@@ -7,7 +7,7 @@ module/submodule IDs and IO data sizes.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
-
import xml.etree.ElementTree as ET
|
|
10
|
+
import xml.etree.ElementTree as ET # nosec B405 — GSDML files are local trusted vendor data
|
|
11
11
|
from dataclasses import dataclass, field
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
from typing import Dict, List, Optional, Union
|
|
@@ -503,7 +503,7 @@ def load_gsdml(path: Union[str, Path]) -> GSDMLDevice:
|
|
|
503
503
|
Returns:
|
|
504
504
|
GSDMLDevice with full module catalog.
|
|
505
505
|
"""
|
|
506
|
-
tree = ET.parse(str(path))
|
|
506
|
+
tree = ET.parse(str(path)) # nosec B314 — GSDML files are local trusted vendor data
|
|
507
507
|
return _parse_gsdml_root(tree.getroot())
|
|
508
508
|
|
|
509
509
|
|
|
@@ -6,9 +6,6 @@ Provides IO-Controller functionality:
|
|
|
6
6
|
- get_station_info(): Resolve device name to connection info
|
|
7
7
|
- Read/Write operations via slot/subslot/index addressing
|
|
8
8
|
|
|
9
|
-
Credits:
|
|
10
|
-
Original implementation by Alfred Krohmer (2015)
|
|
11
|
-
https://github.com/alfredkrohmer/profinet
|
|
12
9
|
"""
|
|
13
10
|
|
|
14
11
|
from __future__ import annotations
|