s7datablock 0.0.1__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.1 → s7datablock-0.0.2}/PKG-INFO +25 -26
- {s7datablock-0.0.1 → s7datablock-0.0.2}/README.md +24 -25
- s7datablock-0.0.2/docs/example.png +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/examples/live_monitor.py +9 -5
- {s7datablock-0.0.1 → s7datablock-0.0.2}/examples/parse_file.py +1 -1
- {s7datablock-0.0.1 → s7datablock-0.0.2}/pyproject.toml +1 -1
- {s7datablock-0.0.1 → s7datablock-0.0.2}/src/s7datablock/mapping.py +3 -2
- {s7datablock-0.0.1 → s7datablock-0.0.2}/src/s7datablock/parser.py +5 -5
- {s7datablock-0.0.1 → s7datablock-0.0.2}/tests/test_dtl_struct.py +1 -1
- {s7datablock-0.0.1 → s7datablock-0.0.2}/tests/test_s7_parsing.py +8 -8
- {s7datablock-0.0.1 → s7datablock-0.0.2}/uv.lock +1 -1
- s7datablock-0.0.1/docs/example.png +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/.coveragerc +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/.github/workflows/ci.yml +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/.gitignore +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/.isort.cfg +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/.pre-commit-config.yaml +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/.readthedocs.yml +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/AUTHORS.md +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/LICENSE.txt +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/examples/basic_usage.py +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/examples/dtl_date.py +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/src/s7datablock/__init__.py +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/src/s7datablock/constants.py +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/src/s7datablock/fields.py +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/src/s7datablock/grammar.py +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/src/s7datablock/py.typed +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/test_buffer_mapping_extended.py +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/tests/conftest.py +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/tests/definitions/s7_1200_out.db +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/tests/definitions/tester.db +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/tests/test_buffer_mapping.py +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/tests/test_buffer_mapping_extended.py +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/tests/test_s7_connector.py +0 -0
- {s7datablock-0.0.1 → s7datablock-0.0.2}/tests/test_s7_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: s7datablock
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
4
4
|
Summary: A Python library for interacting with Siemens S7 PLC datablocks
|
|
5
5
|
Project-URL: Homepage, https://github.com/CEAD-group/s7datablock
|
|
6
6
|
Project-URL: Documentation, https://github.com/CEAD-group/s7datablock
|
|
@@ -45,7 +45,7 @@ Instead of manually mapping byte offsets and types, you can use `.db` definition
|
|
|
45
45
|
- Clean terminal output using [Rich](https://github.com/Textualize/rich)
|
|
46
46
|
- Easy integration into polling and monitoring loops
|
|
47
47
|
|
|
48
|
-

|
|
48
|
+

|
|
49
49
|
|
|
50
50
|
---
|
|
51
51
|
|
|
@@ -92,6 +92,7 @@ df_file_contents = StringIO(
|
|
|
92
92
|
db = S7DataBlock.from_definition_file(df_file_contents, db_number=1200)
|
|
93
93
|
print(db) # This will print the structure of the data block as a table
|
|
94
94
|
|
|
95
|
+
print(repr(db)) # Print the raw representation of the data block
|
|
95
96
|
# In a real application, you would create a snap7 client and connect to the PLC
|
|
96
97
|
|
|
97
98
|
# client = snap7.client.Client()
|
|
@@ -103,10 +104,10 @@ with Live(db.to_table(), refresh_per_second=10) as live:
|
|
|
103
104
|
# db.GET(client)
|
|
104
105
|
|
|
105
106
|
# For demo, we'll just modify some values
|
|
106
|
-
db["
|
|
107
|
-
db["
|
|
108
|
-
db["
|
|
109
|
-
db["
|
|
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
|
|
110
111
|
|
|
111
112
|
# Update the display with current values
|
|
112
113
|
live.update(db.to_table())
|
|
@@ -115,26 +116,24 @@ with Live(db.to_table(), refresh_per_second=10) as live:
|
|
|
115
116
|
|
|
116
117
|
Running this code should render a live table in your terminal
|
|
117
118
|
```text
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
│
|
|
124
|
-
│
|
|
125
|
-
│
|
|
126
|
-
│
|
|
127
|
-
│
|
|
128
|
-
│
|
|
129
|
-
│
|
|
130
|
-
│
|
|
131
|
-
│
|
|
132
|
-
│
|
|
133
|
-
│
|
|
134
|
-
│
|
|
135
|
-
|
|
136
|
-
│ my_udt.Value2 │ Real │ 16.0 │ 4.400 │
|
|
137
|
-
└────────────────────────────┴─────────────┴──────────┴─────────────────────┘
|
|
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
|
+
└───────────────────┴─────────────┴──────────┴─────────────────────┘
|
|
138
137
|
```
|
|
139
138
|
|
|
140
139
|
|
|
@@ -23,7 +23,7 @@ Instead of manually mapping byte offsets and types, you can use `.db` definition
|
|
|
23
23
|
- Clean terminal output using [Rich](https://github.com/Textualize/rich)
|
|
24
24
|
- Easy integration into polling and monitoring loops
|
|
25
25
|
|
|
26
|
-

|
|
26
|
+

|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -70,6 +70,7 @@ df_file_contents = StringIO(
|
|
|
70
70
|
db = S7DataBlock.from_definition_file(df_file_contents, db_number=1200)
|
|
71
71
|
print(db) # This will print the structure of the data block as a table
|
|
72
72
|
|
|
73
|
+
print(repr(db)) # Print the raw representation of the data block
|
|
73
74
|
# In a real application, you would create a snap7 client and connect to the PLC
|
|
74
75
|
|
|
75
76
|
# client = snap7.client.Client()
|
|
@@ -81,10 +82,10 @@ with Live(db.to_table(), refresh_per_second=10) as live:
|
|
|
81
82
|
# db.GET(client)
|
|
82
83
|
|
|
83
84
|
# For demo, we'll just modify some values
|
|
84
|
-
db["
|
|
85
|
-
db["
|
|
86
|
-
db["
|
|
87
|
-
db["
|
|
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
|
|
88
89
|
|
|
89
90
|
# Update the display with current values
|
|
90
91
|
live.update(db.to_table())
|
|
@@ -93,26 +94,24 @@ with Live(db.to_table(), refresh_per_second=10) as live:
|
|
|
93
94
|
|
|
94
95
|
Running this code should render a live table in your terminal
|
|
95
96
|
```text
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
│
|
|
102
|
-
│
|
|
103
|
-
│
|
|
104
|
-
│
|
|
105
|
-
│
|
|
106
|
-
│
|
|
107
|
-
│
|
|
108
|
-
│
|
|
109
|
-
│
|
|
110
|
-
│
|
|
111
|
-
│
|
|
112
|
-
│
|
|
113
|
-
|
|
114
|
-
│ my_udt.Value2 │ Real │ 16.0 │ 4.400 │
|
|
115
|
-
└────────────────────────────┴─────────────┴──────────┴─────────────────────┘
|
|
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
|
+
└───────────────────┴─────────────┴──────────┴─────────────────────┘
|
|
116
115
|
```
|
|
117
116
|
|
|
118
117
|
|
|
Binary file
|
|
@@ -6,6 +6,7 @@ from rich.live import Live
|
|
|
6
6
|
|
|
7
7
|
from s7datablock.mapping import S7DataBlock
|
|
8
8
|
|
|
9
|
+
# Example in-line definition file contents. Alternatively, you can pass a PAth to a *.db file.
|
|
9
10
|
df_file_contents = StringIO(
|
|
10
11
|
"""
|
|
11
12
|
TYPE "my_udt"
|
|
@@ -26,9 +27,12 @@ df_file_contents = StringIO(
|
|
|
26
27
|
"""
|
|
27
28
|
)
|
|
28
29
|
|
|
29
|
-
db = S7DataBlock.from_definition_file(df_file_contents, db_number=1200
|
|
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
|
|
30
32
|
|
|
33
|
+
print(repr(db)) # Print the raw representation of the data block
|
|
31
34
|
# In a real application, you would create a snap7 client and connect to the PLC
|
|
35
|
+
|
|
32
36
|
# client = snap7.client.Client()
|
|
33
37
|
# client.connect('192.168.0.1', 0, 1)
|
|
34
38
|
|
|
@@ -38,10 +42,10 @@ with Live(db.to_table(), refresh_per_second=10) as live:
|
|
|
38
42
|
# db.GET(client)
|
|
39
43
|
|
|
40
44
|
# For demo, we'll just modify some values
|
|
41
|
-
db["
|
|
42
|
-
db["
|
|
43
|
-
db["
|
|
44
|
-
db["
|
|
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
|
|
45
49
|
|
|
46
50
|
# Update the display with current values
|
|
47
51
|
live.update(db.to_table())
|
|
@@ -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
|
|
@@ -172,17 +172,18 @@ class S7DataBlock(BufferMapping):
|
|
|
172
172
|
self.db_size = db_size
|
|
173
173
|
|
|
174
174
|
@classmethod
|
|
175
|
-
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":
|
|
176
176
|
"""Create an S7DataBlock from a TIA Portal export file.
|
|
177
177
|
|
|
178
178
|
Args:
|
|
179
179
|
path: Path to the TIA Portal export file
|
|
180
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.
|
|
181
182
|
|
|
182
183
|
Returns:
|
|
183
184
|
S7DataBlock: The created datablock
|
|
184
185
|
"""
|
|
185
|
-
mapping, size = parse_db_file(path,
|
|
186
|
+
mapping, size = parse_db_file(path, prefix_levels_to_skip=prefix_levels_to_skip)
|
|
186
187
|
return cls(buffer=bytearray(size), mapping=mapping, db_number=db_number, db_size=size)
|
|
187
188
|
|
|
188
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"):
|
|
Binary file
|
|
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
|
|
File without changes
|