siemens-plc 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: siemens-plc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 西门子plc通讯
|
|
5
|
+
Author: LiuWei
|
|
6
|
+
Author-email: 183074632@qq.com
|
|
7
|
+
Requires-Python: >=3.11,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Requires-Dist: python-snap7 (>=2.0.2,<3.0.0)
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# siemens_plc
|
|
16
|
+
通过snap7操作西门子plc
|
|
17
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "siemens-plc"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "西门子plc通讯"
|
|
5
|
+
authors = ["LiuWei <183074632@qq.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
|
|
8
|
+
[tool.poetry.dependencies]
|
|
9
|
+
python = "^3.11"
|
|
10
|
+
python-snap7 = "^2.0.2"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
[build-system]
|
|
14
|
+
requires = ["poetry-core"]
|
|
15
|
+
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""PLC exception base class."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class PLCBaseException(Exception):
|
|
5
|
+
"""Base exception class for PLC-related errors.
|
|
6
|
+
|
|
7
|
+
This exception class inherits from FactoryStandBaseException and serves as
|
|
8
|
+
the base for all exceptions related to PLC operations.
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PLCConnectError(PLCBaseException):
|
|
14
|
+
"""Exception raised for PLC Connect errors.
|
|
15
|
+
|
|
16
|
+
This exception is a subclass of PLCBaseException and is raised when there
|
|
17
|
+
is a Connect error during PLC operations.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PLCReadError(PLCBaseException):
|
|
22
|
+
"""Exception raised for PLC read errors.
|
|
23
|
+
|
|
24
|
+
This exception is a subclass of PLCBaseException and is raised when there
|
|
25
|
+
is an error related to reading data from the PLC.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PLCWriteError(PLCBaseException):
|
|
30
|
+
"""Exception raised for PLC write errors.
|
|
31
|
+
|
|
32
|
+
This exception is a subclass of PLCBaseException and is raised when there is
|
|
33
|
+
an error related to writing data to the PLC.
|
|
34
|
+
"""
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""PLC drive."""
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from typing import Union, Optional
|
|
5
|
+
from threading import Lock
|
|
6
|
+
|
|
7
|
+
import snap7
|
|
8
|
+
from snap7 import util
|
|
9
|
+
|
|
10
|
+
from siemens_plc.exception import PLCReadError, PLCWriteError, PLCConnectError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# pylint: disable=too-many-arguments, too-many-positional-arguments
|
|
14
|
+
class S7PLC:
|
|
15
|
+
"""This class provides methods for interacting with a Siemens S7 PLC using the Snap7 library."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, ip: str, rack: int = 0, slot: int = 1) -> None:
|
|
18
|
+
"""Initialize the S7Plc class."""
|
|
19
|
+
self.ip = ip
|
|
20
|
+
self.rack = rack
|
|
21
|
+
self.slot = slot
|
|
22
|
+
self._s7_client = snap7.client.Client()
|
|
23
|
+
self.plc_lock = Lock()
|
|
24
|
+
|
|
25
|
+
self.logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
def communication_open(self):
|
|
28
|
+
"""Connect to a PLC.
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
PLCConnectError: If there is an error in the connection process.
|
|
32
|
+
"""
|
|
33
|
+
if not self.get_connect_state():
|
|
34
|
+
try:
|
|
35
|
+
self._s7_client.connect(self.ip, self.rack, self.slot)
|
|
36
|
+
self.logger.info("PLC: Connected successfully")
|
|
37
|
+
except RuntimeError as e:
|
|
38
|
+
self.logger.error("PLC: Connection error: %s", e)
|
|
39
|
+
raise PLCConnectError("PLC: Connection error") from e
|
|
40
|
+
|
|
41
|
+
def disconnect(self) -> None:
|
|
42
|
+
"""Disconnect from the PLC."""
|
|
43
|
+
if self.get_connect_state():
|
|
44
|
+
self._s7_client.disconnect()
|
|
45
|
+
self.logger.info("PLC: Disconnected successfully")
|
|
46
|
+
|
|
47
|
+
def get_connect_state(self) -> bool:
|
|
48
|
+
"""Get connect to plc state.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
bool: True is connected, False is not connected.
|
|
52
|
+
"""
|
|
53
|
+
if self._s7_client.get_connected():
|
|
54
|
+
self.logger.info("PLC: Plc connected state is True")
|
|
55
|
+
return True
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
def execute_read(
|
|
59
|
+
self, data_type: str, db_num: int, start: int, size: int, bool_index: int = 0, save_log: bool = True
|
|
60
|
+
) -> Union[str, bool, int]:
|
|
61
|
+
"""读取plc数据的通用方法.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
data_type (str): 读取数据的类型.
|
|
65
|
+
db_num (int): db num.
|
|
66
|
+
start (int): 开始地址位.
|
|
67
|
+
size (int): 地址位长度.
|
|
68
|
+
bool_index (int): bool类型对应的bool index, 默认为0.
|
|
69
|
+
save_log: 是否保存日志, 默认保存.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Union[str, bool, int]: 返回读取plc读取的数据.
|
|
73
|
+
"""
|
|
74
|
+
with self.plc_lock:
|
|
75
|
+
read_func = getattr(self, f"read_{data_type}_data")
|
|
76
|
+
if data_type == "bool":
|
|
77
|
+
return read_func(db_num, start, size, bool_index, save_log)
|
|
78
|
+
return read_func(db_num, start, size, save_log)
|
|
79
|
+
|
|
80
|
+
def read_int_data(self, db_number: int, start: int, size: int = 2, save_log: bool = True) -> int:
|
|
81
|
+
"""Read integer data from the PLC.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
db_number (int): Number of the DB to be read.
|
|
85
|
+
start (int): Byte index to start reading from.
|
|
86
|
+
size (int): Amount of bytes to be read.
|
|
87
|
+
save_log: 是否保存日志, 默认保存.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
int: Status of the read integer data operation.
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
PLCReadError: If reading integer type data fails.
|
|
94
|
+
"""
|
|
95
|
+
response_data = self._s7_client.db_read(db_number, start, size)
|
|
96
|
+
if not response_data:
|
|
97
|
+
raise PLCReadError("PLC: Read integer data error")
|
|
98
|
+
value = util.get_int(response_data, 0)
|
|
99
|
+
if save_log:
|
|
100
|
+
self.logger.info("PLC: Read integer data: %s", value)
|
|
101
|
+
return value
|
|
102
|
+
|
|
103
|
+
def read_real_data(self, db_number: int, start: int, size: int = 4, save_log: bool = True) -> float:
|
|
104
|
+
"""Read real data from the PLC.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
db_number (int): Number of the DB to be read.
|
|
108
|
+
start (int): Byte index to start reading from.
|
|
109
|
+
size (int): Amount of bytes to be read.
|
|
110
|
+
save_log: 是否保存日志, 默认保存.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
int: Value of the read real data operation.
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
PLCReadError: If reading real type data fails.
|
|
117
|
+
"""
|
|
118
|
+
response_data = self._s7_client.db_read(db_number, start, size)
|
|
119
|
+
if not response_data:
|
|
120
|
+
raise PLCReadError("PLC: Read real data error")
|
|
121
|
+
value = util.get_real(response_data, 0)
|
|
122
|
+
if save_log:
|
|
123
|
+
self.logger.info("PLC: Read real data: %s", value)
|
|
124
|
+
return value
|
|
125
|
+
|
|
126
|
+
def read_lreal_data(self, db_number: int, start: int, size: int = 8, save_log: bool = True) -> float:
|
|
127
|
+
"""Read lreal data from the PLC.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
db_number (int): Number of the DB to be read.
|
|
131
|
+
start (int): Byte index to start reading from.
|
|
132
|
+
size (int): Amount of bytes to be read.
|
|
133
|
+
save_log: 是否保存日志, 默认保存.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
int: Value of the read lreal data operation.
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
PLCReadError: If reading lreal type data fails.
|
|
140
|
+
"""
|
|
141
|
+
response_data = self._s7_client.db_read(db_number, start, size)
|
|
142
|
+
if not response_data:
|
|
143
|
+
raise PLCReadError("PLC: Read lreal data error")
|
|
144
|
+
value = util.get_lreal(response_data, 0)
|
|
145
|
+
if save_log:
|
|
146
|
+
self.logger.info("PLC: Read lreal data: %s", value)
|
|
147
|
+
return value
|
|
148
|
+
|
|
149
|
+
def read_bool_data(
|
|
150
|
+
self, db_number: int, start: int, size: int = 1, bool_index: int = 0, save_log: bool = True
|
|
151
|
+
) -> bool:
|
|
152
|
+
"""Read bool data from the PLC.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
db_number (int): Number of the DB to be read.
|
|
156
|
+
start (int): Byte index to start reading from.
|
|
157
|
+
size (int): Amount of bytes to be read.
|
|
158
|
+
bool_index (int): bit index to read from.
|
|
159
|
+
save_log: 是否保存日志, 默认保存.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
bool: Status of the read bool data operation.
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
PLCReadError: If reading bool type data fails.
|
|
166
|
+
"""
|
|
167
|
+
response_data = self._s7_client.db_read(db_number, start, size)
|
|
168
|
+
if not response_data:
|
|
169
|
+
raise PLCReadError("PLC: Read bool data error")
|
|
170
|
+
value = util.get_bool(response_data, 0, bool_index)
|
|
171
|
+
if save_log:
|
|
172
|
+
self.logger.info("PLC: Read bool data: %s", value)
|
|
173
|
+
return value
|
|
174
|
+
|
|
175
|
+
def read_str_data(self, db_number: int, start: int, size: int, save_log: bool = True) -> str:
|
|
176
|
+
"""Read string data from the PLC.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
db_number (int): Number of the DB to be read.
|
|
180
|
+
start (int): Byte index to start reading from.
|
|
181
|
+
size (int): Amount of bytes to be read.
|
|
182
|
+
save_log: 是否保存日志, 默认保存.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
int: Status of the read string data operation.
|
|
186
|
+
|
|
187
|
+
Raises:
|
|
188
|
+
PLCReadError: If reading string type data fails.
|
|
189
|
+
"""
|
|
190
|
+
real_size = size + 2
|
|
191
|
+
response_data = self._s7_client.db_read(db_number, start, real_size)
|
|
192
|
+
if not response_data:
|
|
193
|
+
raise PLCReadError("PLC: Read string data error")
|
|
194
|
+
value = util.get_string(response_data, 0)
|
|
195
|
+
if save_log:
|
|
196
|
+
self.logger.info("PLC: Read string data: %s", value)
|
|
197
|
+
return value
|
|
198
|
+
|
|
199
|
+
def execute_write(
|
|
200
|
+
self, data_type, db_num: int, start: int, data: Union[str, bool, int, float], bool_index: int = 0
|
|
201
|
+
) -> int:
|
|
202
|
+
"""写入plc数据的通用方法.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
data_type (str): 读取数据的类型.
|
|
206
|
+
db_num (int): db num.
|
|
207
|
+
start (int): 开始地址位.
|
|
208
|
+
data (Union[str, bool, int]): 要写入的数据.
|
|
209
|
+
bool_index (int): bool类型对应的bool index.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
int: 写入后的code.
|
|
213
|
+
"""
|
|
214
|
+
with self.plc_lock:
|
|
215
|
+
write_func = getattr(self, f"write_{data_type}_data")
|
|
216
|
+
if data_type == "bool":
|
|
217
|
+
return write_func(db_num, start, data, bool_index)
|
|
218
|
+
return write_func(db_num, start, data)
|
|
219
|
+
|
|
220
|
+
def write_int_data(self, db_number: int, start: int, data: int):
|
|
221
|
+
"""Write integer data to the PLC.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
db_number (int): Number of the DB to be written.
|
|
225
|
+
start (int): Byte index to start writing to.
|
|
226
|
+
data (int): The value to be written.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
int: Status of the write integer data operation.
|
|
230
|
+
|
|
231
|
+
Raises:
|
|
232
|
+
PLCWriteError: If writing integer type data fails.
|
|
233
|
+
"""
|
|
234
|
+
try:
|
|
235
|
+
int_data = bytearray(int.to_bytes(int(data), 2, "big"))
|
|
236
|
+
return self._s7_client.db_write(db_number, start, int_data)
|
|
237
|
+
except RuntimeError as e:
|
|
238
|
+
raise PLCWriteError("PLC: Write integer data error") from e
|
|
239
|
+
|
|
240
|
+
def write_real_data(self, db_number: int, start: int, data: float) -> Optional[int]:
|
|
241
|
+
"""Write real data to the PLC.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
db_number (int): Number of the DB to be written.
|
|
245
|
+
start (int): Byte index to start writing to.
|
|
246
|
+
data (int): The value to be written.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Optional[int]: Status of the write real data operation.
|
|
250
|
+
|
|
251
|
+
Raises:
|
|
252
|
+
PLCWriteError: If writing real type data fails.
|
|
253
|
+
"""
|
|
254
|
+
try:
|
|
255
|
+
real_data_bytearray = bytearray(self._s7_client.db_read(db_number, start, 4))
|
|
256
|
+
util.set_real(real_data_bytearray, 0, data)
|
|
257
|
+
return self._s7_client.db_write(db_number, start, real_data_bytearray)
|
|
258
|
+
except RuntimeError as e:
|
|
259
|
+
raise PLCWriteError("PLC: Write real data error") from e
|
|
260
|
+
|
|
261
|
+
def write_lreal_data(self, db_number: int, start: int, data: float) -> Optional[int]:
|
|
262
|
+
"""Write lreal data to the PLC.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
db_number (int): Number of the DB to be written.
|
|
266
|
+
start (int): Byte index to start writing to.
|
|
267
|
+
data (int): The value to be written.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Optional[int]: Status of the write lreal data operation.
|
|
271
|
+
|
|
272
|
+
Raises:
|
|
273
|
+
PLCWriteError: If writing lreal type data fails.
|
|
274
|
+
"""
|
|
275
|
+
try:
|
|
276
|
+
lreal_data_bytearray = bytearray(self._s7_client.db_read(db_number, start, 8))
|
|
277
|
+
util.set_real(lreal_data_bytearray, 0, data)
|
|
278
|
+
return self._s7_client.db_write(db_number, start, lreal_data_bytearray)
|
|
279
|
+
except RuntimeError as e:
|
|
280
|
+
raise PLCWriteError("PLC: Write lreal data error") from e
|
|
281
|
+
|
|
282
|
+
def write_bool_data(self, db_number: int, start: int, data: bool, bool_index: int):
|
|
283
|
+
"""Write bool data to the PLC.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
db_number (int): Number of the DB to be written.
|
|
287
|
+
start (int): Byte index to start writing to.
|
|
288
|
+
data (bool): The value to be written.
|
|
289
|
+
bool_index (int): bit index to read from, The range is 0-7.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
int: Status of the write string data operation.
|
|
293
|
+
|
|
294
|
+
Raises:
|
|
295
|
+
PLCWriteError: If writing bool type data fails.
|
|
296
|
+
"""
|
|
297
|
+
try:
|
|
298
|
+
int_data_bytearray = bytearray(self._s7_client.db_read(db_number, start, 1))
|
|
299
|
+
util.set_bool(int_data_bytearray, 0, bool_index, data)
|
|
300
|
+
return self._s7_client.db_write(db_number, start, int_data_bytearray)
|
|
301
|
+
except RuntimeError as e:
|
|
302
|
+
raise PLCWriteError("PLC: Write bool data error") from e
|
|
303
|
+
|
|
304
|
+
def write_str_data(self, db_number: int, start: int, data: str):
|
|
305
|
+
"""Write string data to the PLC.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
db_number (int): Number of the DB to be written.
|
|
309
|
+
start (int): Byte index to start writing to.
|
|
310
|
+
data (str): The value to be written.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
int: Status of the write string data operation.
|
|
314
|
+
|
|
315
|
+
Raises:
|
|
316
|
+
PLCWriteError: If writing string type data fails.
|
|
317
|
+
"""
|
|
318
|
+
try:
|
|
319
|
+
str_data = bytearray(
|
|
320
|
+
int.to_bytes(254, 1, "big") +
|
|
321
|
+
int.to_bytes(len(data), 1, "big") +
|
|
322
|
+
data.encode(encoding="ascii")
|
|
323
|
+
)
|
|
324
|
+
return self._s7_client.db_write(db_number, start, str_data)
|
|
325
|
+
except RuntimeError as e:
|
|
326
|
+
raise PLCWriteError("PLC: Write string data error") from e
|