s7datablock 0.0.0a0__tar.gz → 0.0.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.
- s7datablock-0.0.2/PKG-INFO +150 -0
- s7datablock-0.0.2/README.md +128 -0
- s7datablock-0.0.2/docs/example.png +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/examples/basic_usage.py +0 -4
- s7datablock-0.0.2/examples/live_monitor.py +52 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/examples/parse_file.py +1 -1
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/pyproject.toml +4 -4
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/src/s7datablock/mapping.py +23 -17
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/src/s7datablock/parser.py +5 -5
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/tests/test_dtl_struct.py +1 -1
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/tests/test_s7_parsing.py +8 -8
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/uv.lock +1 -1
- s7datablock-0.0.0a0/PKG-INFO +0 -136
- s7datablock-0.0.0a0/README.md +0 -114
- s7datablock-0.0.0a0/docs/Makefile +0 -29
- s7datablock-0.0.0a0/docs/_static/.gitignore +0 -1
- s7datablock-0.0.0a0/docs/authors.md +0 -4
- s7datablock-0.0.0a0/docs/changelog.md +0 -4
- s7datablock-0.0.0a0/docs/conf.py +0 -299
- s7datablock-0.0.0a0/docs/contributing.md +0 -4
- s7datablock-0.0.0a0/docs/index.md +0 -39
- s7datablock-0.0.0a0/docs/license.md +0 -5
- s7datablock-0.0.0a0/docs/readme.md +0 -4
- s7datablock-0.0.0a0/docs/requirements.txt +0 -6
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/.coveragerc +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/.github/workflows/ci.yml +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/.gitignore +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/.isort.cfg +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/.pre-commit-config.yaml +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/.readthedocs.yml +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/AUTHORS.md +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/LICENSE.txt +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/examples/dtl_date.py +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/src/s7datablock/__init__.py +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/src/s7datablock/constants.py +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/src/s7datablock/fields.py +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/src/s7datablock/grammar.py +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/src/s7datablock/py.typed +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/test_buffer_mapping_extended.py +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/tests/conftest.py +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/tests/definitions/s7_1200_out.db +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/tests/definitions/tester.db +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/tests/test_buffer_mapping.py +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/tests/test_buffer_mapping_extended.py +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/tests/test_s7_connector.py +0 -0
- {s7datablock-0.0.0a0 → s7datablock-0.0.2}/tests/test_s7_utils.py +0 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: s7datablock
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: A Python library for interacting with Siemens S7 PLC datablocks
|
|
5
|
+
Project-URL: Homepage, https://github.com/CEAD-group/s7datablock
|
|
6
|
+
Project-URL: Documentation, https://github.com/CEAD-group/s7datablock
|
|
7
|
+
Author-email: Thijs Damsma <thijs@damsma.net>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: AUTHORS.md
|
|
10
|
+
License-File: LICENSE.txt
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Requires-Dist: pyparsing
|
|
15
|
+
Requires-Dist: python-snap7
|
|
16
|
+
Requires-Dist: rich
|
|
17
|
+
Provides-Extra: testing
|
|
18
|
+
Requires-Dist: pytest; extra == 'testing'
|
|
19
|
+
Requires-Dist: pytest-cov; extra == 'testing'
|
|
20
|
+
Requires-Dist: ruff; extra == 'testing'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# S7DataBlock
|
|
24
|
+
|
|
25
|
+
- [S7DataBlock](#s7datablock)
|
|
26
|
+
- [Key Features](#key-features)
|
|
27
|
+
- [Data Transfer](#data-transfer)
|
|
28
|
+
- [Live Monitoring PLC Values](#live-monitoring-plc-values)
|
|
29
|
+
- [Installation](#installation)
|
|
30
|
+
- [Contributing and Issues](#contributing-and-issues)
|
|
31
|
+
|
|
32
|
+
**A Python library for parsing Siemens S7 PLC data blocks, in conjunction with Snap7.**
|
|
33
|
+
|
|
34
|
+
`s7datablock` is a Python utility designed to parse and interpret Siemens S7 PLC data blocks using structured data definitions exported from the TIA Portal. It works seamlessly with the excellent [python-snap7](https://github.com/gijzelaerr/python-snap7) library to enable high-level, dictionary-like access to binary PLC data.
|
|
35
|
+
|
|
36
|
+
Instead of manually mapping byte offsets and types, you can use `.db` definition files exported from TIA Portal to generate a clean, versionable Python representation of your data block structures.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Key Features
|
|
41
|
+
|
|
42
|
+
- Uses exported TIA Portal `*.db` files for accurate structure mapping
|
|
43
|
+
- Read and write entire PLC data blocks with `.GET()` and `.SET()` (or the more pythonic `pull()` and `push()`)
|
|
44
|
+
- Structure and sub-structure access via dot notation
|
|
45
|
+
- Clean terminal output using [Rich](https://github.com/Textualize/rich)
|
|
46
|
+
- Easy integration into polling and monitoring loops
|
|
47
|
+
|
|
48
|
+

|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Data Transfer
|
|
53
|
+
|
|
54
|
+
This library deliberately uses manual .GET() and .SET() calls instead of automatic syncing to prioritize:
|
|
55
|
+
- **Clarity** – Developers control exactly when data is transferred
|
|
56
|
+
- **Performance** – Avoid unnecessary communication overhead
|
|
57
|
+
- **Atomicity** – Batch multiple updates into one write
|
|
58
|
+
|
|
59
|
+
## Live Monitoring PLC Values
|
|
60
|
+
|
|
61
|
+
You can monitor PLC values by creating a polling loop. Here's an example using Rich for clean terminal output:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from time import sleep
|
|
65
|
+
from rich.live import Live
|
|
66
|
+
from datetime import datetime
|
|
67
|
+
from io import StringIO
|
|
68
|
+
|
|
69
|
+
from s7datablock.mapping import S7DataBlock
|
|
70
|
+
|
|
71
|
+
# Example in-line definition file contents. Alternatively, you can pass a PAth to a *.db file.
|
|
72
|
+
df_file_contents = StringIO(
|
|
73
|
+
"""
|
|
74
|
+
TYPE "my_udt"
|
|
75
|
+
VERSION : 0.1
|
|
76
|
+
STRUCT
|
|
77
|
+
PLC_DQ_0 : Bool;
|
|
78
|
+
PLC_DQ_1 : Bool;
|
|
79
|
+
WHEN : DTL;
|
|
80
|
+
Value1 : Int;
|
|
81
|
+
Value2 : Real;
|
|
82
|
+
END_STRUCT;
|
|
83
|
+
END_TYPE
|
|
84
|
+
DATA_BLOCK "my_udt"
|
|
85
|
+
VERSION : 0.1
|
|
86
|
+
"my_udt"
|
|
87
|
+
BEGIN
|
|
88
|
+
END_DATA_BLOCK
|
|
89
|
+
"""
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
db = S7DataBlock.from_definition_file(df_file_contents, db_number=1200)
|
|
93
|
+
print(db) # This will print the structure of the data block as a table
|
|
94
|
+
|
|
95
|
+
print(repr(db)) # Print the raw representation of the data block
|
|
96
|
+
# In a real application, you would create a snap7 client and connect to the PLC
|
|
97
|
+
|
|
98
|
+
# client = snap7.client.Client()
|
|
99
|
+
# client.connect('192.168.0.1', 0, 1)
|
|
100
|
+
|
|
101
|
+
with Live(db.to_table(), refresh_per_second=10) as live:
|
|
102
|
+
while True:
|
|
103
|
+
# In a real application, you would call GET() here
|
|
104
|
+
# db.GET(client)
|
|
105
|
+
|
|
106
|
+
# For demo, we'll just modify some values
|
|
107
|
+
db["WHEN"] = datetime.now()
|
|
108
|
+
db["PLC_DQ_0"] = not db["PLC_DQ_0"] # Toggle the boolean
|
|
109
|
+
db["Value1"] = db["Value1"] + 1 # Increment the integer value
|
|
110
|
+
db["Value2"] = db["Value2"] + 0.1 # Increment the real value
|
|
111
|
+
|
|
112
|
+
# Update the display with current values
|
|
113
|
+
live.update(db.to_table())
|
|
114
|
+
sleep(0.2)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Running this code should render a live table in your terminal
|
|
118
|
+
```text
|
|
119
|
+
Data Block Structure
|
|
120
|
+
┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓
|
|
121
|
+
┃ Name ┃ Data type ┃ Offset ┃ Value ┃
|
|
122
|
+
┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━┩
|
|
123
|
+
│ PLC_DQ_0 │ Bool │ 0.0 │ True │
|
|
124
|
+
│ PLC_DQ_1 │ Bool │ 0.1 │ False │
|
|
125
|
+
│ WHEN │ DTL │ 2.0 │ 2025-07-14 17:08:40 │
|
|
126
|
+
│ WHEN.YEAR │ UInt │ 2.0 │ 2025 │
|
|
127
|
+
│ WHEN.MONTH │ USInt │ 4.0 │ 7 │
|
|
128
|
+
│ WHEN.DAY │ USInt │ 5.0 │ 14 │
|
|
129
|
+
│ WHEN.WEEKDAY │ USInt │ 6.0 │ 1 │
|
|
130
|
+
│ WHEN.HOUR │ USInt │ 7.0 │ 17 │
|
|
131
|
+
│ WHEN.MINUTE │ USInt │ 8.0 │ 8 │
|
|
132
|
+
│ WHEN.SECOND │ USInt │ 9.0 │ 40 │
|
|
133
|
+
│ WHEN.NANOSECOND │ UDInt │ 10.0 │ 568441000 │
|
|
134
|
+
│ Value1 │ Int │ 14.0 │ 757 │
|
|
135
|
+
│ Value2 │ Real │ 16.0 │ 75.699 │
|
|
136
|
+
└───────────────────┴─────────────┴──────────┴─────────────────────┘
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
## Installation
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
pip install s7datablock
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
## Contributing and Issues
|
|
149
|
+
|
|
150
|
+
The library is in its early stages, and contributions are welcome. Feel free to submit a pull request or open an issue on GitHub.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# S7DataBlock
|
|
2
|
+
|
|
3
|
+
- [S7DataBlock](#s7datablock)
|
|
4
|
+
- [Key Features](#key-features)
|
|
5
|
+
- [Data Transfer](#data-transfer)
|
|
6
|
+
- [Live Monitoring PLC Values](#live-monitoring-plc-values)
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Contributing and Issues](#contributing-and-issues)
|
|
9
|
+
|
|
10
|
+
**A Python library for parsing Siemens S7 PLC data blocks, in conjunction with Snap7.**
|
|
11
|
+
|
|
12
|
+
`s7datablock` is a Python utility designed to parse and interpret Siemens S7 PLC data blocks using structured data definitions exported from the TIA Portal. It works seamlessly with the excellent [python-snap7](https://github.com/gijzelaerr/python-snap7) library to enable high-level, dictionary-like access to binary PLC data.
|
|
13
|
+
|
|
14
|
+
Instead of manually mapping byte offsets and types, you can use `.db` definition files exported from TIA Portal to generate a clean, versionable Python representation of your data block structures.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Key Features
|
|
19
|
+
|
|
20
|
+
- Uses exported TIA Portal `*.db` files for accurate structure mapping
|
|
21
|
+
- Read and write entire PLC data blocks with `.GET()` and `.SET()` (or the more pythonic `pull()` and `push()`)
|
|
22
|
+
- Structure and sub-structure access via dot notation
|
|
23
|
+
- Clean terminal output using [Rich](https://github.com/Textualize/rich)
|
|
24
|
+
- Easy integration into polling and monitoring loops
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Data Transfer
|
|
31
|
+
|
|
32
|
+
This library deliberately uses manual .GET() and .SET() calls instead of automatic syncing to prioritize:
|
|
33
|
+
- **Clarity** – Developers control exactly when data is transferred
|
|
34
|
+
- **Performance** – Avoid unnecessary communication overhead
|
|
35
|
+
- **Atomicity** – Batch multiple updates into one write
|
|
36
|
+
|
|
37
|
+
## Live Monitoring PLC Values
|
|
38
|
+
|
|
39
|
+
You can monitor PLC values by creating a polling loop. Here's an example using Rich for clean terminal output:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from time import sleep
|
|
43
|
+
from rich.live import Live
|
|
44
|
+
from datetime import datetime
|
|
45
|
+
from io import StringIO
|
|
46
|
+
|
|
47
|
+
from s7datablock.mapping import S7DataBlock
|
|
48
|
+
|
|
49
|
+
# Example in-line definition file contents. Alternatively, you can pass a PAth to a *.db file.
|
|
50
|
+
df_file_contents = StringIO(
|
|
51
|
+
"""
|
|
52
|
+
TYPE "my_udt"
|
|
53
|
+
VERSION : 0.1
|
|
54
|
+
STRUCT
|
|
55
|
+
PLC_DQ_0 : Bool;
|
|
56
|
+
PLC_DQ_1 : Bool;
|
|
57
|
+
WHEN : DTL;
|
|
58
|
+
Value1 : Int;
|
|
59
|
+
Value2 : Real;
|
|
60
|
+
END_STRUCT;
|
|
61
|
+
END_TYPE
|
|
62
|
+
DATA_BLOCK "my_udt"
|
|
63
|
+
VERSION : 0.1
|
|
64
|
+
"my_udt"
|
|
65
|
+
BEGIN
|
|
66
|
+
END_DATA_BLOCK
|
|
67
|
+
"""
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
db = S7DataBlock.from_definition_file(df_file_contents, db_number=1200)
|
|
71
|
+
print(db) # This will print the structure of the data block as a table
|
|
72
|
+
|
|
73
|
+
print(repr(db)) # Print the raw representation of the data block
|
|
74
|
+
# In a real application, you would create a snap7 client and connect to the PLC
|
|
75
|
+
|
|
76
|
+
# client = snap7.client.Client()
|
|
77
|
+
# client.connect('192.168.0.1', 0, 1)
|
|
78
|
+
|
|
79
|
+
with Live(db.to_table(), refresh_per_second=10) as live:
|
|
80
|
+
while True:
|
|
81
|
+
# In a real application, you would call GET() here
|
|
82
|
+
# db.GET(client)
|
|
83
|
+
|
|
84
|
+
# For demo, we'll just modify some values
|
|
85
|
+
db["WHEN"] = datetime.now()
|
|
86
|
+
db["PLC_DQ_0"] = not db["PLC_DQ_0"] # Toggle the boolean
|
|
87
|
+
db["Value1"] = db["Value1"] + 1 # Increment the integer value
|
|
88
|
+
db["Value2"] = db["Value2"] + 0.1 # Increment the real value
|
|
89
|
+
|
|
90
|
+
# Update the display with current values
|
|
91
|
+
live.update(db.to_table())
|
|
92
|
+
sleep(0.2)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Running this code should render a live table in your terminal
|
|
96
|
+
```text
|
|
97
|
+
Data Block Structure
|
|
98
|
+
┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓
|
|
99
|
+
┃ Name ┃ Data type ┃ Offset ┃ Value ┃
|
|
100
|
+
┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━┩
|
|
101
|
+
│ PLC_DQ_0 │ Bool │ 0.0 │ True │
|
|
102
|
+
│ PLC_DQ_1 │ Bool │ 0.1 │ False │
|
|
103
|
+
│ WHEN │ DTL │ 2.0 │ 2025-07-14 17:08:40 │
|
|
104
|
+
│ WHEN.YEAR │ UInt │ 2.0 │ 2025 │
|
|
105
|
+
│ WHEN.MONTH │ USInt │ 4.0 │ 7 │
|
|
106
|
+
│ WHEN.DAY │ USInt │ 5.0 │ 14 │
|
|
107
|
+
│ WHEN.WEEKDAY │ USInt │ 6.0 │ 1 │
|
|
108
|
+
│ WHEN.HOUR │ USInt │ 7.0 │ 17 │
|
|
109
|
+
│ WHEN.MINUTE │ USInt │ 8.0 │ 8 │
|
|
110
|
+
│ WHEN.SECOND │ USInt │ 9.0 │ 40 │
|
|
111
|
+
│ WHEN.NANOSECOND │ UDInt │ 10.0 │ 568441000 │
|
|
112
|
+
│ Value1 │ Int │ 14.0 │ 757 │
|
|
113
|
+
│ Value2 │ Real │ 16.0 │ 75.699 │
|
|
114
|
+
└───────────────────┴─────────────┴──────────┴─────────────────────┘
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
## Installation
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
pip install s7datablock
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
## Contributing and Issues
|
|
127
|
+
|
|
128
|
+
The library is in its early stages, and contributions are welcome. Feel free to submit a pull request or open an issue on GitHub.
|
|
Binary file
|
|
@@ -24,10 +24,6 @@ def main():
|
|
|
24
24
|
buffered_vars["setpointHz2"] = 23.5
|
|
25
25
|
print("Buffer after setting setpointHz2 (hex):", " ".join(f"{b:02X}" for b in buffered_vars.buffer))
|
|
26
26
|
|
|
27
|
-
# Display the full structure with values
|
|
28
|
-
print("\nFull structure:")
|
|
29
|
-
print(buffered_vars)
|
|
30
|
-
|
|
31
27
|
|
|
32
28
|
if __name__ == "__main__":
|
|
33
29
|
main()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from io import StringIO
|
|
3
|
+
from time import sleep
|
|
4
|
+
|
|
5
|
+
from rich.live import Live
|
|
6
|
+
|
|
7
|
+
from s7datablock.mapping import S7DataBlock
|
|
8
|
+
|
|
9
|
+
# Example in-line definition file contents. Alternatively, you can pass a PAth to a *.db file.
|
|
10
|
+
df_file_contents = StringIO(
|
|
11
|
+
"""
|
|
12
|
+
TYPE "my_udt"
|
|
13
|
+
VERSION : 0.1
|
|
14
|
+
STRUCT
|
|
15
|
+
PLC_DQ_0 : Bool;
|
|
16
|
+
PLC_DQ_1 : Bool;
|
|
17
|
+
WHEN : DTL;
|
|
18
|
+
Value1 : Int;
|
|
19
|
+
Value2 : Real;
|
|
20
|
+
END_STRUCT;
|
|
21
|
+
END_TYPE
|
|
22
|
+
DATA_BLOCK "my_udt"
|
|
23
|
+
VERSION : 0.1
|
|
24
|
+
"my_udt"
|
|
25
|
+
BEGIN
|
|
26
|
+
END_DATA_BLOCK
|
|
27
|
+
"""
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
db = S7DataBlock.from_definition_file(df_file_contents, db_number=1200)
|
|
31
|
+
print(db) # This will print the structure of the data block as a table
|
|
32
|
+
|
|
33
|
+
print(repr(db)) # Print the raw representation of the data block
|
|
34
|
+
# In a real application, you would create a snap7 client and connect to the PLC
|
|
35
|
+
|
|
36
|
+
# client = snap7.client.Client()
|
|
37
|
+
# client.connect('192.168.0.1', 0, 1)
|
|
38
|
+
|
|
39
|
+
with Live(db.to_table(), refresh_per_second=10) as live:
|
|
40
|
+
while True:
|
|
41
|
+
# In a real application, you would call GET() here
|
|
42
|
+
# db.GET(client)
|
|
43
|
+
|
|
44
|
+
# For demo, we'll just modify some values
|
|
45
|
+
db["WHEN"] = datetime.now()
|
|
46
|
+
db["PLC_DQ_0"] = not db["PLC_DQ_0"] # Toggle the boolean
|
|
47
|
+
db["Value1"] = db["Value1"] + 1 # Increment the integer value
|
|
48
|
+
db["Value2"] = db["Value2"] + 0.1 # Increment the real value
|
|
49
|
+
|
|
50
|
+
# Update the display with current values
|
|
51
|
+
live.update(db.to_table())
|
|
52
|
+
sleep(0.2)
|
|
@@ -22,7 +22,7 @@ END_DATA_BLOCK
|
|
|
22
22
|
"""
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
-
db = S7DataBlock.from_definition_file(db_buff, db_number=1200,
|
|
25
|
+
db = S7DataBlock.from_definition_file(db_buff, db_number=1200, prefix_levels_to_skip=0)
|
|
26
26
|
|
|
27
27
|
# Set initial datetime value
|
|
28
28
|
now = datetime(2025, 7, 9, 14, 30, 45, 123456) # microseconds = 123456
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "s7datablock"
|
|
3
|
-
version = "0.0.
|
|
4
|
-
description = "A Python library interacting with Siemens S7 PLC datablocks"
|
|
3
|
+
version = "0.0.2"
|
|
4
|
+
description = "A Python library for interacting with Siemens S7 PLC datablocks"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
7
7
|
{ name = "Thijs Damsma", email = "thijs@damsma.net" }
|
|
@@ -36,8 +36,8 @@ line-length = 120
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
[project.urls]
|
|
39
|
-
Homepage = "https://github.com/
|
|
40
|
-
Documentation = "https://
|
|
39
|
+
Homepage = "https://github.com/CEAD-group/s7datablock"
|
|
40
|
+
Documentation = "https://github.com/CEAD-group/s7datablock"
|
|
41
41
|
|
|
42
42
|
[project.optional-dependencies]
|
|
43
43
|
testing = [
|
|
@@ -68,6 +68,21 @@ class BufferMapping(MutableMapping[str, ElementalType]):
|
|
|
68
68
|
Returns a formatted string showing field names, data types, offsets, and current values
|
|
69
69
|
with proper hierarchical indentation.
|
|
70
70
|
"""
|
|
71
|
+
total_size = self.total_size()
|
|
72
|
+
table = self.to_table()
|
|
73
|
+
|
|
74
|
+
console = Console(record=True, width=120)
|
|
75
|
+
with console.capture() as capture:
|
|
76
|
+
console.print() # Empty line before table
|
|
77
|
+
for _ in range(17):
|
|
78
|
+
console.print(" ", end="")
|
|
79
|
+
console.print(table)
|
|
80
|
+
console.print(f" Total size: {total_size} bytes")
|
|
81
|
+
console.print() # Empty line after
|
|
82
|
+
|
|
83
|
+
return capture.get()
|
|
84
|
+
|
|
85
|
+
def to_table(self) -> Table:
|
|
71
86
|
table = Table(
|
|
72
87
|
show_edge=True,
|
|
73
88
|
show_header=True,
|
|
@@ -112,22 +127,12 @@ class BufferMapping(MutableMapping[str, ElementalType]):
|
|
|
112
127
|
table.add_row(*field.get_table_row(name=name, indent_level=indent_level, buffer=self.buffer))
|
|
113
128
|
prev_sub = sub
|
|
114
129
|
|
|
115
|
-
|
|
116
|
-
total_size = max(
|
|
117
|
-
field.offset[0] + 2 if isinstance(field.offset, tuple) else field.offset + field.struct_width
|
|
118
|
-
for field in self.mapping.values()
|
|
119
|
-
)
|
|
130
|
+
return table
|
|
120
131
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
console.print(" ", end="")
|
|
126
|
-
console.print(table)
|
|
127
|
-
console.print(f" Total size: {total_size} bytes")
|
|
128
|
-
console.print() # Empty line after
|
|
129
|
-
|
|
130
|
-
return capture.get()
|
|
132
|
+
def total_size(self) -> int:
|
|
133
|
+
"""Calculate the total size of the buffer based on the maximum offset of all fields."""
|
|
134
|
+
total_size = max(field.offset + field.struct_width for field in self.mapping.values())
|
|
135
|
+
return total_size
|
|
131
136
|
|
|
132
137
|
def keys(self):
|
|
133
138
|
"""Returns a view of the field names in the mapping."""
|
|
@@ -167,17 +172,18 @@ class S7DataBlock(BufferMapping):
|
|
|
167
172
|
self.db_size = db_size
|
|
168
173
|
|
|
169
174
|
@classmethod
|
|
170
|
-
def from_definition_file(cls, path, db_number: int | None = None,
|
|
175
|
+
def from_definition_file(cls, path, db_number: int | None = None, prefix_levels_to_skip: int = 1) -> "S7DataBlock":
|
|
171
176
|
"""Create an S7DataBlock from a TIA Portal export file.
|
|
172
177
|
|
|
173
178
|
Args:
|
|
174
179
|
path: Path to the TIA Portal export file
|
|
175
180
|
db_number: Optional DB number for PLC communication
|
|
181
|
+
prefix_levels_to_skip: How many prefix levels to skip when generating fieldnames. Default=1 skips the UDT name.
|
|
176
182
|
|
|
177
183
|
Returns:
|
|
178
184
|
S7DataBlock: The created datablock
|
|
179
185
|
"""
|
|
180
|
-
mapping, size = parse_db_file(path,
|
|
186
|
+
mapping, size = parse_db_file(path, prefix_levels_to_skip=prefix_levels_to_skip)
|
|
181
187
|
return cls(buffer=bytearray(size), mapping=mapping, db_number=db_number, db_size=size)
|
|
182
188
|
|
|
183
189
|
def GET(self, client: snap7.client.Client):
|
|
@@ -20,12 +20,12 @@ def resolve_data_types(types: dict[str, Any], prefix: list[str], d: Any) -> Iter
|
|
|
20
20
|
yield prefix, d
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
def parse_db_file(p: Path | StringIO,
|
|
23
|
+
def parse_db_file(p: Path | StringIO, prefix_levels_to_skip=1) -> DBParseResult:
|
|
24
24
|
"""Parse a DB file and return a S7Field mapping and total size.
|
|
25
25
|
|
|
26
26
|
Args:
|
|
27
27
|
p (Path): Path to the DB file
|
|
28
|
-
|
|
28
|
+
prefix_levels_to_skip (int, optional): How many prefix levels to skip when generating fieldnames. Default=1 skips the UDT name.
|
|
29
29
|
|
|
30
30
|
Returns:
|
|
31
31
|
tuple[dict[str, S7Field], int]: The S7Field mapping and total size in bytes
|
|
@@ -37,8 +37,8 @@ def parse_db_file(p: Path | StringIO, nesting_depth_to_skip=1) -> DBParseResult:
|
|
|
37
37
|
else:
|
|
38
38
|
raise TypeError("Input must be a Path or StringIO")
|
|
39
39
|
|
|
40
|
-
if
|
|
41
|
-
raise ValueError("
|
|
40
|
+
if prefix_levels_to_skip < 0:
|
|
41
|
+
raise ValueError("prefix_levels_to_skip must be a non-negative integer")
|
|
42
42
|
result = program.parseString(string, parse_all=True).as_dict()
|
|
43
43
|
types = result["TYPES"]
|
|
44
44
|
data = result["DATA_BLOCK"]
|
|
@@ -53,7 +53,7 @@ def parse_db_file(p: Path | StringIO, nesting_depth_to_skip=1) -> DBParseResult:
|
|
|
53
53
|
current_bools: list[str] = []
|
|
54
54
|
prev_base_name = ""
|
|
55
55
|
for i in name_types:
|
|
56
|
-
name_parts = i.name[
|
|
56
|
+
name_parts = i.name[prefix_levels_to_skip:]
|
|
57
57
|
full_name = ".".join(name_parts)
|
|
58
58
|
base_name = ".".join(name_parts[:-1])
|
|
59
59
|
if current_bools and (base_name != prev_base_name or i.type != "Bool"):
|
|
@@ -26,7 +26,7 @@ BEGIN
|
|
|
26
26
|
END_DATA_BLOCK
|
|
27
27
|
"""
|
|
28
28
|
)
|
|
29
|
-
return S7DataBlock.from_definition_file(db_buff, db_number=1200,
|
|
29
|
+
return S7DataBlock.from_definition_file(db_buff, db_number=1200, prefix_levels_to_skip=0)
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def test_db_structure(sample_dtl_db):
|
|
@@ -129,9 +129,9 @@ def test_parse_db_from_stringio():
|
|
|
129
129
|
assert "PLC_DQ_0" in result.mapping
|
|
130
130
|
|
|
131
131
|
|
|
132
|
-
def
|
|
133
|
-
"""Test parsing with
|
|
134
|
-
result = parse_db_file(Path("tests/definitions/s7_1200_out.db"),
|
|
132
|
+
def test_parse_db_no_prefix_levels_to_skip():
|
|
133
|
+
"""Test parsing with prefix_levels_to_skip=0 to keep full S7Field names."""
|
|
134
|
+
result = parse_db_file(Path("tests/definitions/s7_1200_out.db"), prefix_levels_to_skip=0)
|
|
135
135
|
|
|
136
136
|
assert isinstance(result, DBParseResult)
|
|
137
137
|
assert result.length == 16
|
|
@@ -154,9 +154,9 @@ def test_parse_db_no_nesting_depth_to_skip():
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
|
|
157
|
-
def
|
|
158
|
-
"""Test parsing with
|
|
159
|
-
result = parse_db_file(Path("tests/definitions/s7_1200_out.db"),
|
|
157
|
+
def test_parse_db_with_prefix_levels_to_skip():
|
|
158
|
+
"""Test parsing with prefix_levels_to_skip=1 to skip top-level UDT name."""
|
|
159
|
+
result = parse_db_file(Path("tests/definitions/s7_1200_out.db"), prefix_levels_to_skip=1)
|
|
160
160
|
|
|
161
161
|
assert isinstance(result, DBParseResult)
|
|
162
162
|
assert result.length == 16
|
|
@@ -182,8 +182,8 @@ def test_parse_db_with_nesting_depth_to_skip():
|
|
|
182
182
|
def test_parse_db_invalid_input(sample_db_file):
|
|
183
183
|
"""Test that invalid inputs raise appropriate exceptions."""
|
|
184
184
|
# Test invalid nesting depth
|
|
185
|
-
with pytest.raises(ValueError, match="
|
|
186
|
-
parse_db_file(sample_db_file,
|
|
185
|
+
with pytest.raises(ValueError, match="prefix_levels_to_skip must be a non-negative integer"):
|
|
186
|
+
parse_db_file(sample_db_file, prefix_levels_to_skip=-1)
|
|
187
187
|
|
|
188
188
|
# Test invalid input type
|
|
189
189
|
with pytest.raises(TypeError, match="Input must be a Path or StringIO"):
|
s7datablock-0.0.0a0/PKG-INFO
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: s7datablock
|
|
3
|
-
Version: 0.0.0a0
|
|
4
|
-
Summary: A Python library interacting with Siemens S7 PLC datablocks
|
|
5
|
-
Project-URL: Homepage, https://github.com/pyscaffold/pyscaffold/
|
|
6
|
-
Project-URL: Documentation, https://pyscaffold.org/
|
|
7
|
-
Author-email: Thijs Damsma <thijs@damsma.net>
|
|
8
|
-
License: MIT
|
|
9
|
-
License-File: AUTHORS.md
|
|
10
|
-
License-File: LICENSE.txt
|
|
11
|
-
Classifier: Development Status :: 4 - Beta
|
|
12
|
-
Classifier: Programming Language :: Python
|
|
13
|
-
Requires-Python: >=3.11
|
|
14
|
-
Requires-Dist: pyparsing
|
|
15
|
-
Requires-Dist: python-snap7
|
|
16
|
-
Requires-Dist: rich
|
|
17
|
-
Provides-Extra: testing
|
|
18
|
-
Requires-Dist: pytest; extra == 'testing'
|
|
19
|
-
Requires-Dist: pytest-cov; extra == 'testing'
|
|
20
|
-
Requires-Dist: ruff; extra == 'testing'
|
|
21
|
-
Description-Content-Type: text/markdown
|
|
22
|
-
|
|
23
|
-
# S7DataBlock
|
|
24
|
-
|
|
25
|
-
- [S7DataBlock](#s7datablock)
|
|
26
|
-
- [Introduction](#introduction)
|
|
27
|
-
- [Installation](#installation)
|
|
28
|
-
- [Parsing Data Blocks](#parsing-data-blocks)
|
|
29
|
-
- [Contributing and Issues](#contributing-and-issues)
|
|
30
|
-
|
|
31
|
-
## Introduction
|
|
32
|
-
|
|
33
|
-
> A Python library for parsing Siemens S7 PLC data blocks, in conjunction with Snap7.
|
|
34
|
-
|
|
35
|
-
`s7datablock` is a Python library designed to parse and interpret Siemens S7 PLC data blocks. It works in conjunction with the [python-snap7](https://github.com/gijzelaerr/python-snap7) library, providing a set of tools and utilities to read and write the bytes in a PLC datablock as structured data, using the DB definitions as exported from a TIA Portal project. This makes it easier for developers and engineers to work with Siemens S7 PLCs in their Python applications.
|
|
36
|
-
|
|
37
|
-
Instead of manually mapping bytes, the dataformat defined in Tia Portal can be used to generate the appropriate Python mapping. The configuration file can be kept separate from the code, making it easier to track versions and support complicated data structures.
|
|
38
|
-
|
|
39
|
-
## Installation
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
pip install s7datablock
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### Parsing Data Blocks
|
|
46
|
-
|
|
47
|
-
In this example, we will demonstrate how to use the `s7datablock` library to parse and interpret Siemens S7 PLC data blocks.
|
|
48
|
-
|
|
49
|
-
First, we define a s7_1200_out user-defined type (UDT) and a data block in Structured Control Language (SCL). The UDT, named "s7_1200_out_udt", consists of five boolean variables and one integer variable. The data block, named "s7_1200_out", uses this UDT. This file was defined in and exported from Tia Portal, using the `generate source from blocks, including all dependent blocks` option.
|
|
50
|
-
|
|
51
|
-
```scl
|
|
52
|
-
# s7_1200_out.db
|
|
53
|
-
TYPE "s7_1200_out_udt"
|
|
54
|
-
VERSION : 0.1
|
|
55
|
-
STRUCT
|
|
56
|
-
PLC_DQ_0 : Bool; # Boolean variable
|
|
57
|
-
PLC_DQ_1 : Bool; # Boolean variable
|
|
58
|
-
PLC_DQ_2 : Bool; # Boolean variable
|
|
59
|
-
PLC_DQ_3 : Bool; # Boolean variable
|
|
60
|
-
PLC_DQ_4 : Bool; # Boolean variable
|
|
61
|
-
SB_AQ_0 : Int; # Integer variable
|
|
62
|
-
END_STRUCT;
|
|
63
|
-
END_TYPE
|
|
64
|
-
|
|
65
|
-
DATA_BLOCK "s7_1200_out"
|
|
66
|
-
{ S7_Optimized_Access := 'FALSE' }
|
|
67
|
-
VERSION : 0.1
|
|
68
|
-
NON_RETAIN
|
|
69
|
-
"s7_1200_out_udt"
|
|
70
|
-
|
|
71
|
-
BEGIN
|
|
72
|
-
|
|
73
|
-
END_DATA_BLOCK
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Next, we use the `S7DataBlock` class from the `s7datablock` library to parse the data block definition file. We then display the parsed data block and its initial buffer state. Finally, we modify one of the boolean variables in the data block and display the updated buffer state.
|
|
77
|
-
|
|
78
|
-
```python
|
|
79
|
-
# Import necessary modules
|
|
80
|
-
from s7datablock.mapping import S7DataBlock
|
|
81
|
-
from pathlib import Path
|
|
82
|
-
|
|
83
|
-
# Parse the data block definition file
|
|
84
|
-
db1200 = S7DataBlock.from_definition_file(
|
|
85
|
-
Path("tests/definitions/s7_1200_out.db"),
|
|
86
|
-
db_number=1200
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
# The printing the db1200 datasrutcure produces a nicely formatted output
|
|
90
|
-
print(db1200)
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
```raw
|
|
94
|
-
Data Block Structure
|
|
95
|
-
┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━┓
|
|
96
|
-
┃ Name ┃ Data type ┃ Offset ┃ Value ┃
|
|
97
|
-
┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━┩
|
|
98
|
-
│ PLC_DQ_0 │ Bool │ 0.0 │ False │
|
|
99
|
-
│ PLC_DQ_1 │ Bool │ 0.1 │ False │
|
|
100
|
-
│ PLC_DQ_2 │ Bool │ 0.2 │ False │
|
|
101
|
-
│ PLC_DQ_3 │ Bool │ 0.3 │ False │
|
|
102
|
-
│ PLC_DQ_4 │ Bool │ 0.4 │ False │
|
|
103
|
-
│ SB_AQ_0 │ Int │ 2.0 │ 0 │
|
|
104
|
-
│ TIMEFIELD │ DTL │ 4.0 │ None │
|
|
105
|
-
│ YEAR │ UInt │ 4.0 │ 0 │
|
|
106
|
-
│ MONTH │ USInt │ 6.0 │ 0 │
|
|
107
|
-
│ DAY │ USInt │ 7.0 │ 0 │
|
|
108
|
-
│ WEEKDAY │ USInt │ 8.0 │ 0 │
|
|
109
|
-
│ HOUR │ USInt │ 9.0 │ 0 │
|
|
110
|
-
│ MINUTE │ USInt │ 10.0 │ 0 │
|
|
111
|
-
│ SECOND │ USInt │ 11.0 │ 0 │
|
|
112
|
-
│ NANOSECOND │ UDInt │ 12.0 │ 0 │
|
|
113
|
-
└──────────────────┴─────────────┴──────────┴─────────────┘
|
|
114
|
-
Total size: 16 bytes
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
```python
|
|
118
|
-
# db1200 maps the data like a dictionary but the hood it stores the data in a continuous buffer,
|
|
119
|
-
# using the same format and byte ordering as the PLC
|
|
120
|
-
# Initially the buffer is only zeros
|
|
121
|
-
print(db1200.buffer)
|
|
122
|
-
# Output: bytearray(b'\x00\x00\x00\x00')
|
|
123
|
-
|
|
124
|
-
# Modify one of the boolean variables
|
|
125
|
-
db1200["PLC_DQ_3"] = True
|
|
126
|
-
|
|
127
|
-
# Now one bit has changed
|
|
128
|
-
print(db1200.buffer)
|
|
129
|
-
# Output: bytearray(b'\x08\x00\x00\x00')
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
## Contributing and Issues
|
|
135
|
-
|
|
136
|
-
The library is in its early stages, and contributions are welcome. Feel free to submit a pull request or open an issue on GitHub.
|
s7datablock-0.0.0a0/README.md
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# S7DataBlock
|
|
2
|
-
|
|
3
|
-
- [S7DataBlock](#s7datablock)
|
|
4
|
-
- [Introduction](#introduction)
|
|
5
|
-
- [Installation](#installation)
|
|
6
|
-
- [Parsing Data Blocks](#parsing-data-blocks)
|
|
7
|
-
- [Contributing and Issues](#contributing-and-issues)
|
|
8
|
-
|
|
9
|
-
## Introduction
|
|
10
|
-
|
|
11
|
-
> A Python library for parsing Siemens S7 PLC data blocks, in conjunction with Snap7.
|
|
12
|
-
|
|
13
|
-
`s7datablock` is a Python library designed to parse and interpret Siemens S7 PLC data blocks. It works in conjunction with the [python-snap7](https://github.com/gijzelaerr/python-snap7) library, providing a set of tools and utilities to read and write the bytes in a PLC datablock as structured data, using the DB definitions as exported from a TIA Portal project. This makes it easier for developers and engineers to work with Siemens S7 PLCs in their Python applications.
|
|
14
|
-
|
|
15
|
-
Instead of manually mapping bytes, the dataformat defined in Tia Portal can be used to generate the appropriate Python mapping. The configuration file can be kept separate from the code, making it easier to track versions and support complicated data structures.
|
|
16
|
-
|
|
17
|
-
## Installation
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
pip install s7datablock
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### Parsing Data Blocks
|
|
24
|
-
|
|
25
|
-
In this example, we will demonstrate how to use the `s7datablock` library to parse and interpret Siemens S7 PLC data blocks.
|
|
26
|
-
|
|
27
|
-
First, we define a s7_1200_out user-defined type (UDT) and a data block in Structured Control Language (SCL). The UDT, named "s7_1200_out_udt", consists of five boolean variables and one integer variable. The data block, named "s7_1200_out", uses this UDT. This file was defined in and exported from Tia Portal, using the `generate source from blocks, including all dependent blocks` option.
|
|
28
|
-
|
|
29
|
-
```scl
|
|
30
|
-
# s7_1200_out.db
|
|
31
|
-
TYPE "s7_1200_out_udt"
|
|
32
|
-
VERSION : 0.1
|
|
33
|
-
STRUCT
|
|
34
|
-
PLC_DQ_0 : Bool; # Boolean variable
|
|
35
|
-
PLC_DQ_1 : Bool; # Boolean variable
|
|
36
|
-
PLC_DQ_2 : Bool; # Boolean variable
|
|
37
|
-
PLC_DQ_3 : Bool; # Boolean variable
|
|
38
|
-
PLC_DQ_4 : Bool; # Boolean variable
|
|
39
|
-
SB_AQ_0 : Int; # Integer variable
|
|
40
|
-
END_STRUCT;
|
|
41
|
-
END_TYPE
|
|
42
|
-
|
|
43
|
-
DATA_BLOCK "s7_1200_out"
|
|
44
|
-
{ S7_Optimized_Access := 'FALSE' }
|
|
45
|
-
VERSION : 0.1
|
|
46
|
-
NON_RETAIN
|
|
47
|
-
"s7_1200_out_udt"
|
|
48
|
-
|
|
49
|
-
BEGIN
|
|
50
|
-
|
|
51
|
-
END_DATA_BLOCK
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Next, we use the `S7DataBlock` class from the `s7datablock` library to parse the data block definition file. We then display the parsed data block and its initial buffer state. Finally, we modify one of the boolean variables in the data block and display the updated buffer state.
|
|
55
|
-
|
|
56
|
-
```python
|
|
57
|
-
# Import necessary modules
|
|
58
|
-
from s7datablock.mapping import S7DataBlock
|
|
59
|
-
from pathlib import Path
|
|
60
|
-
|
|
61
|
-
# Parse the data block definition file
|
|
62
|
-
db1200 = S7DataBlock.from_definition_file(
|
|
63
|
-
Path("tests/definitions/s7_1200_out.db"),
|
|
64
|
-
db_number=1200
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
# The printing the db1200 datasrutcure produces a nicely formatted output
|
|
68
|
-
print(db1200)
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
```raw
|
|
72
|
-
Data Block Structure
|
|
73
|
-
┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━┓
|
|
74
|
-
┃ Name ┃ Data type ┃ Offset ┃ Value ┃
|
|
75
|
-
┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━┩
|
|
76
|
-
│ PLC_DQ_0 │ Bool │ 0.0 │ False │
|
|
77
|
-
│ PLC_DQ_1 │ Bool │ 0.1 │ False │
|
|
78
|
-
│ PLC_DQ_2 │ Bool │ 0.2 │ False │
|
|
79
|
-
│ PLC_DQ_3 │ Bool │ 0.3 │ False │
|
|
80
|
-
│ PLC_DQ_4 │ Bool │ 0.4 │ False │
|
|
81
|
-
│ SB_AQ_0 │ Int │ 2.0 │ 0 │
|
|
82
|
-
│ TIMEFIELD │ DTL │ 4.0 │ None │
|
|
83
|
-
│ YEAR │ UInt │ 4.0 │ 0 │
|
|
84
|
-
│ MONTH │ USInt │ 6.0 │ 0 │
|
|
85
|
-
│ DAY │ USInt │ 7.0 │ 0 │
|
|
86
|
-
│ WEEKDAY │ USInt │ 8.0 │ 0 │
|
|
87
|
-
│ HOUR │ USInt │ 9.0 │ 0 │
|
|
88
|
-
│ MINUTE │ USInt │ 10.0 │ 0 │
|
|
89
|
-
│ SECOND │ USInt │ 11.0 │ 0 │
|
|
90
|
-
│ NANOSECOND │ UDInt │ 12.0 │ 0 │
|
|
91
|
-
└──────────────────┴─────────────┴──────────┴─────────────┘
|
|
92
|
-
Total size: 16 bytes
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
```python
|
|
96
|
-
# db1200 maps the data like a dictionary but the hood it stores the data in a continuous buffer,
|
|
97
|
-
# using the same format and byte ordering as the PLC
|
|
98
|
-
# Initially the buffer is only zeros
|
|
99
|
-
print(db1200.buffer)
|
|
100
|
-
# Output: bytearray(b'\x00\x00\x00\x00')
|
|
101
|
-
|
|
102
|
-
# Modify one of the boolean variables
|
|
103
|
-
db1200["PLC_DQ_3"] = True
|
|
104
|
-
|
|
105
|
-
# Now one bit has changed
|
|
106
|
-
print(db1200.buffer)
|
|
107
|
-
# Output: bytearray(b'\x08\x00\x00\x00')
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
## Contributing and Issues
|
|
113
|
-
|
|
114
|
-
The library is in its early stages, and contributions are welcome. Feel free to submit a pull request or open an issue on GitHub.
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# Makefile for Sphinx documentation
|
|
2
|
-
#
|
|
3
|
-
|
|
4
|
-
# You can set these variables from the command line, and also
|
|
5
|
-
# from the environment for the first two.
|
|
6
|
-
SPHINXOPTS ?=
|
|
7
|
-
SPHINXBUILD ?= sphinx-build
|
|
8
|
-
SOURCEDIR = .
|
|
9
|
-
BUILDDIR = _build
|
|
10
|
-
AUTODOCDIR = api
|
|
11
|
-
|
|
12
|
-
# User-friendly check for sphinx-build
|
|
13
|
-
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $?), 1)
|
|
14
|
-
$(error "The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from https://sphinx-doc.org/")
|
|
15
|
-
endif
|
|
16
|
-
|
|
17
|
-
.PHONY: help clean Makefile
|
|
18
|
-
|
|
19
|
-
# Put it first so that "make" without argument is like "make help".
|
|
20
|
-
help:
|
|
21
|
-
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
22
|
-
|
|
23
|
-
clean:
|
|
24
|
-
rm -rf $(BUILDDIR)/* $(AUTODOCDIR)
|
|
25
|
-
|
|
26
|
-
# Catch-all target: route all unknown targets to Sphinx using the new
|
|
27
|
-
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
|
28
|
-
%: Makefile
|
|
29
|
-
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# Empty directory
|
s7datablock-0.0.0a0/docs/conf.py
DELETED
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
# This file is execfile()d with the current directory set to its containing dir.
|
|
2
|
-
#
|
|
3
|
-
# This file only contains a selection of the most common options. For a full
|
|
4
|
-
# list see the documentation:
|
|
5
|
-
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
|
6
|
-
#
|
|
7
|
-
# All configuration values have a default; values that are commented out
|
|
8
|
-
# serve to show the default.
|
|
9
|
-
|
|
10
|
-
import os
|
|
11
|
-
import sys
|
|
12
|
-
import shutil
|
|
13
|
-
|
|
14
|
-
# -- Path setup --------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
__location__ = os.path.dirname(__file__)
|
|
17
|
-
|
|
18
|
-
# If extensions (or modules to document with autodoc) are in another directory,
|
|
19
|
-
# add these directories to sys.path here. If the directory is relative to the
|
|
20
|
-
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
21
|
-
sys.path.insert(0, os.path.join(__location__, "../src"))
|
|
22
|
-
|
|
23
|
-
# -- Run sphinx-apidoc -------------------------------------------------------
|
|
24
|
-
# This hack is necessary since RTD does not issue `sphinx-apidoc` before running
|
|
25
|
-
# `sphinx-build -b html . _build/html`. See Issue:
|
|
26
|
-
# https://github.com/readthedocs/readthedocs.org/issues/1139
|
|
27
|
-
# DON'T FORGET: Check the box "Install your project inside a virtualenv using
|
|
28
|
-
# setup.py install" in the RTD Advanced Settings.
|
|
29
|
-
# Additionally it helps us to avoid running apidoc manually
|
|
30
|
-
|
|
31
|
-
try: # for Sphinx >= 1.7
|
|
32
|
-
from sphinx.ext import apidoc
|
|
33
|
-
except ImportError:
|
|
34
|
-
from sphinx import apidoc
|
|
35
|
-
|
|
36
|
-
output_dir = os.path.join(__location__, "api")
|
|
37
|
-
module_dir = os.path.join(__location__, "../src/s7datablock")
|
|
38
|
-
try:
|
|
39
|
-
shutil.rmtree(output_dir)
|
|
40
|
-
except FileNotFoundError:
|
|
41
|
-
pass
|
|
42
|
-
|
|
43
|
-
try:
|
|
44
|
-
import sphinx
|
|
45
|
-
|
|
46
|
-
cmd_line = f"sphinx-apidoc --implicit-namespaces -f -o {output_dir} {module_dir}"
|
|
47
|
-
|
|
48
|
-
args = cmd_line.split(" ")
|
|
49
|
-
if tuple(sphinx.__version__.split(".")) >= ("1", "7"):
|
|
50
|
-
# This is a rudimentary parse_version to avoid external dependencies
|
|
51
|
-
args = args[1:]
|
|
52
|
-
|
|
53
|
-
apidoc.main(args)
|
|
54
|
-
except Exception as e:
|
|
55
|
-
print("Running `sphinx-apidoc` failed!\n{}".format(e))
|
|
56
|
-
|
|
57
|
-
# -- General configuration ---------------------------------------------------
|
|
58
|
-
|
|
59
|
-
# If your documentation needs a minimal Sphinx version, state it here.
|
|
60
|
-
# needs_sphinx = '1.0'
|
|
61
|
-
|
|
62
|
-
# Add any Sphinx extension module names here, as strings. They can be extensions
|
|
63
|
-
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
|
64
|
-
extensions = [
|
|
65
|
-
"sphinx.ext.autodoc",
|
|
66
|
-
"sphinx.ext.intersphinx",
|
|
67
|
-
"sphinx.ext.todo",
|
|
68
|
-
"sphinx.ext.autosummary",
|
|
69
|
-
"sphinx.ext.viewcode",
|
|
70
|
-
"sphinx.ext.coverage",
|
|
71
|
-
"sphinx.ext.doctest",
|
|
72
|
-
"sphinx.ext.ifconfig",
|
|
73
|
-
"sphinx.ext.mathjax",
|
|
74
|
-
"sphinx.ext.napoleon",
|
|
75
|
-
]
|
|
76
|
-
|
|
77
|
-
# Add any paths that contain templates here, relative to this directory.
|
|
78
|
-
templates_path = ["_templates"]
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
# Enable markdown
|
|
82
|
-
extensions.append("myst_parser")
|
|
83
|
-
|
|
84
|
-
# Configure MyST-Parser
|
|
85
|
-
myst_enable_extensions = [
|
|
86
|
-
"amsmath",
|
|
87
|
-
"colon_fence",
|
|
88
|
-
"deflist",
|
|
89
|
-
"dollarmath",
|
|
90
|
-
"html_image",
|
|
91
|
-
"linkify",
|
|
92
|
-
"replacements",
|
|
93
|
-
"smartquotes",
|
|
94
|
-
"substitution",
|
|
95
|
-
"tasklist",
|
|
96
|
-
]
|
|
97
|
-
|
|
98
|
-
# The suffix of source filenames.
|
|
99
|
-
source_suffix = [".rst", ".md"]
|
|
100
|
-
|
|
101
|
-
# The encoding of source files.
|
|
102
|
-
# source_encoding = 'utf-8-sig'
|
|
103
|
-
|
|
104
|
-
# The master toctree document.
|
|
105
|
-
master_doc = "index"
|
|
106
|
-
|
|
107
|
-
# General information about the project.
|
|
108
|
-
project = "s7datablock"
|
|
109
|
-
copyright = "2023, Thijs Damsma"
|
|
110
|
-
|
|
111
|
-
# The version info for the project you're documenting, acts as replacement for
|
|
112
|
-
# |version| and |release|, also used in various other places throughout the
|
|
113
|
-
# built documents.
|
|
114
|
-
#
|
|
115
|
-
# version: The short X.Y version.
|
|
116
|
-
# release: The full version, including alpha/beta/rc tags.
|
|
117
|
-
# If you don’t need the separation provided between version and release,
|
|
118
|
-
# just set them both to the same value.
|
|
119
|
-
try:
|
|
120
|
-
from s7datablock import __version__ as version
|
|
121
|
-
except ImportError:
|
|
122
|
-
version = ""
|
|
123
|
-
|
|
124
|
-
if not version or version.lower() == "unknown":
|
|
125
|
-
version = os.getenv("READTHEDOCS_VERSION", "unknown") # automatically set by RTD
|
|
126
|
-
|
|
127
|
-
release = version
|
|
128
|
-
|
|
129
|
-
# The language for content autogenerated by Sphinx. Refer to documentation
|
|
130
|
-
# for a list of supported languages.
|
|
131
|
-
# language = None
|
|
132
|
-
|
|
133
|
-
# There are two options for replacing |today|: either, you set today to some
|
|
134
|
-
# non-false value, then it is used:
|
|
135
|
-
# today = ''
|
|
136
|
-
# Else, today_fmt is used as the format for a strftime call.
|
|
137
|
-
# today_fmt = '%B %d, %Y'
|
|
138
|
-
|
|
139
|
-
# List of patterns, relative to source directory, that match files and
|
|
140
|
-
# directories to ignore when looking for source files.
|
|
141
|
-
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".venv"]
|
|
142
|
-
|
|
143
|
-
# The reST default role (used for this markup: `text`) to use for all documents.
|
|
144
|
-
# default_role = None
|
|
145
|
-
|
|
146
|
-
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
147
|
-
# add_function_parentheses = True
|
|
148
|
-
|
|
149
|
-
# If true, the current module name will be prepended to all description
|
|
150
|
-
# unit titles (such as .. function::).
|
|
151
|
-
# add_module_names = True
|
|
152
|
-
|
|
153
|
-
# If true, sectionauthor and moduleauthor directives will be shown in the
|
|
154
|
-
# output. They are ignored by default.
|
|
155
|
-
# show_authors = False
|
|
156
|
-
|
|
157
|
-
# The name of the Pygments (syntax highlighting) style to use.
|
|
158
|
-
pygments_style = "sphinx"
|
|
159
|
-
|
|
160
|
-
# A list of ignored prefixes for module index sorting.
|
|
161
|
-
# modindex_common_prefix = []
|
|
162
|
-
|
|
163
|
-
# If true, keep warnings as "system message" paragraphs in the built documents.
|
|
164
|
-
# keep_warnings = False
|
|
165
|
-
|
|
166
|
-
# If this is True, todo emits a warning for each TODO entries. The default is False.
|
|
167
|
-
todo_emit_warnings = True
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
# -- Options for HTML output -------------------------------------------------
|
|
171
|
-
|
|
172
|
-
# The theme to use for HTML and HTML Help pages. See the documentation for
|
|
173
|
-
# a list of builtin themes.
|
|
174
|
-
html_theme = "alabaster"
|
|
175
|
-
|
|
176
|
-
# Theme options are theme-specific and customize the look and feel of a theme
|
|
177
|
-
# further. For a list of options available for each theme, see the
|
|
178
|
-
# documentation.
|
|
179
|
-
html_theme_options = {"sidebar_width": "300px", "page_width": "1200px"}
|
|
180
|
-
|
|
181
|
-
# Add any paths that contain custom themes here, relative to this directory.
|
|
182
|
-
# html_theme_path = []
|
|
183
|
-
|
|
184
|
-
# The name for this set of Sphinx documents. If None, it defaults to
|
|
185
|
-
# "<project> v<release> documentation".
|
|
186
|
-
# html_title = None
|
|
187
|
-
|
|
188
|
-
# A shorter title for the navigation bar. Default is the same as html_title.
|
|
189
|
-
# html_short_title = None
|
|
190
|
-
|
|
191
|
-
# The name of an image file (relative to this directory) to place at the top
|
|
192
|
-
# of the sidebar.
|
|
193
|
-
# html_logo = ""
|
|
194
|
-
|
|
195
|
-
# The name of an image file (within the static path) to use as favicon of the
|
|
196
|
-
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
|
197
|
-
# pixels large.
|
|
198
|
-
# html_favicon = None
|
|
199
|
-
|
|
200
|
-
# Add any paths that contain custom static files (such as style sheets) here,
|
|
201
|
-
# relative to this directory. They are copied after the builtin static files,
|
|
202
|
-
# so a file named "default.css" will overwrite the builtin "default.css".
|
|
203
|
-
html_static_path = ["_static"]
|
|
204
|
-
|
|
205
|
-
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
|
206
|
-
# using the given strftime format.
|
|
207
|
-
# html_last_updated_fmt = '%b %d, %Y'
|
|
208
|
-
|
|
209
|
-
# If true, SmartyPants will be used to convert quotes and dashes to
|
|
210
|
-
# typographically correct entities.
|
|
211
|
-
# html_use_smartypants = True
|
|
212
|
-
|
|
213
|
-
# Custom sidebar templates, maps document names to template names.
|
|
214
|
-
# html_sidebars = {}
|
|
215
|
-
|
|
216
|
-
# Additional templates that should be rendered to pages, maps page names to
|
|
217
|
-
# template names.
|
|
218
|
-
# html_additional_pages = {}
|
|
219
|
-
|
|
220
|
-
# If false, no module index is generated.
|
|
221
|
-
# html_domain_indices = True
|
|
222
|
-
|
|
223
|
-
# If false, no index is generated.
|
|
224
|
-
# html_use_index = True
|
|
225
|
-
|
|
226
|
-
# If true, the index is split into individual pages for each letter.
|
|
227
|
-
# html_split_index = False
|
|
228
|
-
|
|
229
|
-
# If true, links to the reST sources are added to the pages.
|
|
230
|
-
# html_show_sourcelink = True
|
|
231
|
-
|
|
232
|
-
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
|
233
|
-
# html_show_sphinx = True
|
|
234
|
-
|
|
235
|
-
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
|
236
|
-
# html_show_copyright = True
|
|
237
|
-
|
|
238
|
-
# If true, an OpenSearch description file will be output, and all pages will
|
|
239
|
-
# contain a <link> tag referring to it. The value of this option must be the
|
|
240
|
-
# base URL from which the finished HTML is served.
|
|
241
|
-
# html_use_opensearch = ''
|
|
242
|
-
|
|
243
|
-
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
|
244
|
-
# html_file_suffix = None
|
|
245
|
-
|
|
246
|
-
# Output file base name for HTML help builder.
|
|
247
|
-
htmlhelp_basename = "s7datablock-doc"
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
# -- Options for LaTeX output ------------------------------------------------
|
|
251
|
-
|
|
252
|
-
latex_elements = {
|
|
253
|
-
# The paper size ("letterpaper" or "a4paper").
|
|
254
|
-
# "papersize": "letterpaper",
|
|
255
|
-
# The font size ("10pt", "11pt" or "12pt").
|
|
256
|
-
# "pointsize": "10pt",
|
|
257
|
-
# Additional stuff for the LaTeX preamble.
|
|
258
|
-
# "preamble": "",
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
# Grouping the document tree into LaTeX files. List of tuples
|
|
262
|
-
# (source start file, target name, title, author, documentclass [howto/manual]).
|
|
263
|
-
latex_documents = [("index", "user_guide.tex", "s7datablock Documentation", "Thijs Damsma", "manual")]
|
|
264
|
-
|
|
265
|
-
# The name of an image file (relative to this directory) to place at the top of
|
|
266
|
-
# the title page.
|
|
267
|
-
# latex_logo = ""
|
|
268
|
-
|
|
269
|
-
# For "manual" documents, if this is true, then toplevel headings are parts,
|
|
270
|
-
# not chapters.
|
|
271
|
-
# latex_use_parts = False
|
|
272
|
-
|
|
273
|
-
# If true, show page references after internal links.
|
|
274
|
-
# latex_show_pagerefs = False
|
|
275
|
-
|
|
276
|
-
# If true, show URL addresses after external links.
|
|
277
|
-
# latex_show_urls = False
|
|
278
|
-
|
|
279
|
-
# Documents to append as an appendix to all manuals.
|
|
280
|
-
# latex_appendices = []
|
|
281
|
-
|
|
282
|
-
# If false, no module index is generated.
|
|
283
|
-
# latex_domain_indices = True
|
|
284
|
-
|
|
285
|
-
# -- External mapping --------------------------------------------------------
|
|
286
|
-
python_version = ".".join(map(str, sys.version_info[0:2]))
|
|
287
|
-
intersphinx_mapping = {
|
|
288
|
-
"sphinx": ("https://www.sphinx-doc.org/en/master", None),
|
|
289
|
-
"python": ("https://docs.python.org/" + python_version, None),
|
|
290
|
-
"matplotlib": ("https://matplotlib.org", None),
|
|
291
|
-
"numpy": ("https://numpy.org/doc/stable", None),
|
|
292
|
-
"sklearn": ("https://scikit-learn.org/stable", None),
|
|
293
|
-
"pandas": ("https://pandas.pydata.org/pandas-docs/stable", None),
|
|
294
|
-
"scipy": ("https://docs.scipy.org/doc/scipy/reference", None),
|
|
295
|
-
"setuptools": ("https://setuptools.pypa.io/en/stable/", None),
|
|
296
|
-
"pyscaffold": ("https://pyscaffold.org/en/stable", None),
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
print(f"loading configurations for {project} {version} ...", file=sys.stderr)
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# s7datablock
|
|
2
|
-
|
|
3
|
-
Add a short description here!
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
## Note
|
|
7
|
-
|
|
8
|
-
> This is the main page of your project's [Sphinx] documentation. It is
|
|
9
|
-
> formatted in [Markdown]. Add additional pages by creating md-files in
|
|
10
|
-
> `docs` or rst-files (formatted in [reStructuredText]) and adding links to
|
|
11
|
-
> them in the `Contents` section below.
|
|
12
|
-
>
|
|
13
|
-
> Please check [Sphinx] and [MyST] for more information
|
|
14
|
-
> about how to document your project and how to configure your preferences.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
## Contents
|
|
18
|
-
|
|
19
|
-
```{toctree}
|
|
20
|
-
:maxdepth: 2
|
|
21
|
-
|
|
22
|
-
Overview <readme>
|
|
23
|
-
Contributions & Help <contributing>
|
|
24
|
-
License <license>
|
|
25
|
-
Authors <authors>
|
|
26
|
-
Changelog <changelog>
|
|
27
|
-
Module Reference <api/modules>
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Indices and tables
|
|
31
|
-
|
|
32
|
-
* {ref}`genindex`
|
|
33
|
-
* {ref}`modindex`
|
|
34
|
-
* {ref}`search`
|
|
35
|
-
|
|
36
|
-
[Sphinx]: http://www.sphinx-doc.org/
|
|
37
|
-
[Markdown]: https://daringfireball.net/projects/markdown/
|
|
38
|
-
[reStructuredText]: http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
|
|
39
|
-
[MyST]: https://myst-parser.readthedocs.io/en/latest/
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|