slmp-connect-python 1.0.0__tar.gz → 1.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.
- {slmp_connect_python-1.0.0/slmp_connect_python.egg-info → slmp_connect_python-1.1.0}/PKG-INFO +7 -1
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/README.md +6 -0
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/pyproject.toml +1 -1
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/__init__.py +1 -1
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/_operations.py +12 -12
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/async_client.py +1 -1
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/device_ranges.py +1 -1
- slmp_connect_python-1.1.0/slmp/error_codes.py +45 -0
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/utils.py +119 -102
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0/slmp_connect_python.egg-info}/PKG-INFO +7 -1
- slmp_connect_python-1.0.0/slmp/error_codes.py +0 -2107
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/LICENSE +0 -0
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/MANIFEST.in +0 -0
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/setup.cfg +0 -0
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/cli.py +0 -0
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/client.py +0 -0
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/constants.py +0 -0
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/core.py +0 -0
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/errors.py +0 -0
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/py.typed +0 -0
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp_connect_python.egg-info/SOURCES.txt +0 -0
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp_connect_python.egg-info/dependency_links.txt +0 -0
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp_connect_python.egg-info/entry_points.txt +0 -0
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp_connect_python.egg-info/requires.txt +0 -0
- {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp_connect_python.egg-info/top_level.txt +0 -0
{slmp_connect_python-1.0.0/slmp_connect_python.egg-info → slmp_connect_python-1.1.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: slmp-connect-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: SLMP Connect Python: client library for MELSEC SLMP binary communication
|
|
5
5
|
Author: fa-yoshinobu
|
|
6
6
|
License-Expression: MIT
|
|
@@ -93,3 +93,9 @@ See that page for verified PLC models, transports, dates, limitations, and retai
|
|
|
93
93
|
| License | [MIT](LICENSE) |
|
|
94
94
|
| Registry | [PyPI](https://pypi.org/project/slmp-connect-python/) |
|
|
95
95
|
| Package | `slmp-connect-python` |
|
|
96
|
+
|
|
97
|
+
## Commercial support
|
|
98
|
+
|
|
99
|
+
If you plan to embed this library in a paid or commercial product, please consider a separate support agreement or supporting the project as a sponsor.
|
|
100
|
+
|
|
101
|
+
Contact: <https://fa-labo.com/contact.html>
|
|
@@ -59,3 +59,9 @@ See that page for verified PLC models, transports, dates, limitations, and retai
|
|
|
59
59
|
| License | [MIT](LICENSE) |
|
|
60
60
|
| Registry | [PyPI](https://pypi.org/project/slmp-connect-python/) |
|
|
61
61
|
| Package | `slmp-connect-python` |
|
|
62
|
+
|
|
63
|
+
## Commercial support
|
|
64
|
+
|
|
65
|
+
If you plan to embed this library in a paid or commercial product, please consider a separate support agreement or supporting the project as a sponsor.
|
|
66
|
+
|
|
67
|
+
Contact: <https://fa-labo.com/contact.html>
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "slmp-connect-python"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.1.0"
|
|
8
8
|
description = "SLMP Connect Python: client library for MELSEC SLMP binary communication"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -85,7 +85,7 @@ def _effective_series(series: PLCSeries | str | None, default_series: PLCSeries)
|
|
|
85
85
|
return PLCSeries(series) if series is not None else default_series
|
|
86
86
|
|
|
87
87
|
|
|
88
|
-
def
|
|
88
|
+
def _parse_device_for_address_profile(device: str | DeviceRef, address_profile: object | None) -> DeviceRef:
|
|
89
89
|
ref = parse_device(device, plc_profile=address_profile)
|
|
90
90
|
return _require_explicit_plc_profile_for_xy(device, address_profile, ref)
|
|
91
91
|
|
|
@@ -110,7 +110,7 @@ def build_read_devices_request(
|
|
|
110
110
|
) -> OperationRequest:
|
|
111
111
|
_check_direct_device_points(points, bit_unit=bit_unit, name="read_devices")
|
|
112
112
|
effective_series = _effective_series(series, default_series)
|
|
113
|
-
ref =
|
|
113
|
+
ref = _parse_device_for_address_profile(device, address_profile)
|
|
114
114
|
_validate_direct_read_device(ref, points=points, bit_unit=bit_unit)
|
|
115
115
|
_check_temporarily_unsupported_device(ref)
|
|
116
116
|
_warn_practical_device_path(ref, series=effective_series, access_kind="direct")
|
|
@@ -154,7 +154,7 @@ def build_write_devices_request(
|
|
|
154
154
|
raise ValueError("values must not be empty")
|
|
155
155
|
_check_direct_device_points(len(values), bit_unit=bit_unit, name="write_devices")
|
|
156
156
|
effective_series = _effective_series(series, default_series)
|
|
157
|
-
ref =
|
|
157
|
+
ref = _parse_device_for_address_profile(device, address_profile)
|
|
158
158
|
_validate_direct_write_device(ref, bit_unit=bit_unit)
|
|
159
159
|
_check_temporarily_unsupported_device(ref)
|
|
160
160
|
_warn_practical_device_path(ref, series=effective_series, access_kind="direct")
|
|
@@ -188,7 +188,7 @@ def build_read_dwords_request(
|
|
|
188
188
|
) -> OperationRequest:
|
|
189
189
|
if count < 1:
|
|
190
190
|
raise ValueError("count must be >= 1")
|
|
191
|
-
ref =
|
|
191
|
+
ref = _parse_device_for_address_profile(device, address_profile)
|
|
192
192
|
_validate_direct_dword_read_device(ref)
|
|
193
193
|
return build_read_devices_request(
|
|
194
194
|
ref,
|
|
@@ -342,12 +342,12 @@ def build_register_monitor_devices_request(
|
|
|
342
342
|
word_refs: list[DeviceRef] = []
|
|
343
343
|
dword_refs: list[DeviceRef] = []
|
|
344
344
|
for dev in word_devices:
|
|
345
|
-
ref =
|
|
345
|
+
ref = _parse_device_for_address_profile(dev, address_profile)
|
|
346
346
|
_check_temporarily_unsupported_device(ref)
|
|
347
347
|
payload += encode_device_spec(ref, series=effective_series)
|
|
348
348
|
word_refs.append(ref)
|
|
349
349
|
for dev in dword_devices:
|
|
350
|
-
ref =
|
|
350
|
+
ref = _parse_device_for_address_profile(dev, address_profile)
|
|
351
351
|
_check_temporarily_unsupported_device(ref)
|
|
352
352
|
payload += encode_device_spec(ref, series=effective_series)
|
|
353
353
|
dword_refs.append(ref)
|
|
@@ -499,8 +499,8 @@ def build_read_random_request(
|
|
|
499
499
|
)
|
|
500
500
|
subcommand = resolve_device_subcommand(bit_unit=False, series=effective_series, extension=False)
|
|
501
501
|
|
|
502
|
-
words = [
|
|
503
|
-
dwords = [
|
|
502
|
+
words = [_parse_device_for_address_profile(device, address_profile) for device in word_devices]
|
|
503
|
+
dwords = [_parse_device_for_address_profile(device, address_profile) for device in dword_devices]
|
|
504
504
|
_validate_random_read_devices(words, dwords)
|
|
505
505
|
_check_temporarily_unsupported_devices(words)
|
|
506
506
|
_check_temporarily_unsupported_devices(dwords)
|
|
@@ -718,7 +718,7 @@ def build_read_block_request(
|
|
|
718
718
|
normalized_bit: list[tuple[DeviceRef, int]] = []
|
|
719
719
|
for device, points in word_blocks:
|
|
720
720
|
_check_points_u16(points, "word_block points")
|
|
721
|
-
ref =
|
|
721
|
+
ref = _parse_device_for_address_profile(device, address_profile)
|
|
722
722
|
_check_temporarily_unsupported_device(ref)
|
|
723
723
|
_warn_practical_device_path(ref, series=effective_series, access_kind="direct")
|
|
724
724
|
normalized_word.append((ref, points))
|
|
@@ -726,7 +726,7 @@ def build_read_block_request(
|
|
|
726
726
|
payload += points.to_bytes(2, "little")
|
|
727
727
|
for device, points in bit_blocks:
|
|
728
728
|
_check_points_u16(points, "bit_block points")
|
|
729
|
-
ref =
|
|
729
|
+
ref = _parse_device_for_address_profile(device, address_profile)
|
|
730
730
|
_check_temporarily_unsupported_device(ref)
|
|
731
731
|
_warn_practical_device_path(ref, series=effective_series, access_kind="direct")
|
|
732
732
|
normalized_bit.append((ref, points))
|
|
@@ -784,13 +784,13 @@ def build_write_block_request(
|
|
|
784
784
|
word_refs: list[DeviceRef] = []
|
|
785
785
|
bit_refs: list[DeviceRef] = []
|
|
786
786
|
for device, values in word_blocks:
|
|
787
|
-
ref =
|
|
787
|
+
ref = _parse_device_for_address_profile(device, address_profile)
|
|
788
788
|
_check_temporarily_unsupported_device(ref)
|
|
789
789
|
_warn_practical_device_path(ref, series=effective_series, access_kind="direct")
|
|
790
790
|
_check_points_u16(len(values), "word block size")
|
|
791
791
|
word_refs.append(ref)
|
|
792
792
|
for device, values in bit_blocks:
|
|
793
|
-
ref =
|
|
793
|
+
ref = _parse_device_for_address_profile(device, address_profile)
|
|
794
794
|
_check_temporarily_unsupported_device(ref)
|
|
795
795
|
_warn_practical_device_path(ref, series=effective_series, access_kind="direct")
|
|
796
796
|
_check_points_u16(len(values), "bit block size")
|
|
@@ -91,7 +91,7 @@ class AsyncSlmpClient:
|
|
|
91
91
|
|
|
92
92
|
The standard async client route requires ``plc_profile`` and fixes the
|
|
93
93
|
frame type, access profile, and address/range handling from that one
|
|
94
|
-
explicit
|
|
94
|
+
explicit PLC profile.
|
|
95
95
|
"""
|
|
96
96
|
self.host = host
|
|
97
97
|
self.transport_type = transport.lower()
|
|
@@ -518,7 +518,7 @@ def normalize_plc_profile(value: SlmpPlcProfile | str) -> SlmpPlcProfile:
|
|
|
518
518
|
|
|
519
519
|
if isinstance(value, SlmpPlcProfile):
|
|
520
520
|
return value
|
|
521
|
-
normalized = str(value).strip()
|
|
521
|
+
normalized = str(value).strip()
|
|
522
522
|
if normalized in _CANONICAL_FAMILIES:
|
|
523
523
|
return _CANONICAL_FAMILIES[normalized]
|
|
524
524
|
supported = ", ".join(sorted(_CANONICAL_FAMILIES))
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""SLMP end-code keys and categories."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
SlmpEndCodeLanguage = Literal["en", "ja"]
|
|
8
|
+
|
|
9
|
+
_REMOTE_PASSWORD_END_CODES = {
|
|
10
|
+
0xC200,
|
|
11
|
+
0xC201,
|
|
12
|
+
0xC202,
|
|
13
|
+
0xC203,
|
|
14
|
+
0xC204,
|
|
15
|
+
0xC205,
|
|
16
|
+
0xC810,
|
|
17
|
+
0xC811,
|
|
18
|
+
0xC812,
|
|
19
|
+
0xC813,
|
|
20
|
+
0xC814,
|
|
21
|
+
0xC815,
|
|
22
|
+
0xC816,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_end_code_name(end_code: int) -> str:
|
|
27
|
+
"""Return the stable code-derived key for an SLMP end code."""
|
|
28
|
+
return f"slmp_end_code_{int(end_code) & 0xFFFF:04x}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_end_code_message(end_code: int, language: SlmpEndCodeLanguage = "en") -> str | None:
|
|
32
|
+
"""Return a user-facing message for an SLMP end code.
|
|
33
|
+
|
|
34
|
+
Localized message text is not embedded in this package. Resolve
|
|
35
|
+
get_end_code_name(end_code) in an application-owned catalog when text is
|
|
36
|
+
required.
|
|
37
|
+
"""
|
|
38
|
+
_ = end_code
|
|
39
|
+
_ = language
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def is_remote_password_end_code(end_code: int) -> bool:
|
|
44
|
+
"""Return True if the SLMP end code is related to remote password protection."""
|
|
45
|
+
return (int(end_code) & 0xFFFF) in _REMOTE_PASSWORD_END_CODES
|