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.
Files changed (25) hide show
  1. {slmp_connect_python-1.0.0/slmp_connect_python.egg-info → slmp_connect_python-1.1.0}/PKG-INFO +7 -1
  2. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/README.md +6 -0
  3. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/pyproject.toml +1 -1
  4. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/__init__.py +1 -1
  5. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/_operations.py +12 -12
  6. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/async_client.py +1 -1
  7. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/device_ranges.py +1 -1
  8. slmp_connect_python-1.1.0/slmp/error_codes.py +45 -0
  9. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/utils.py +119 -102
  10. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0/slmp_connect_python.egg-info}/PKG-INFO +7 -1
  11. slmp_connect_python-1.0.0/slmp/error_codes.py +0 -2107
  12. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/LICENSE +0 -0
  13. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/MANIFEST.in +0 -0
  14. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/setup.cfg +0 -0
  15. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/cli.py +0 -0
  16. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/client.py +0 -0
  17. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/constants.py +0 -0
  18. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/core.py +0 -0
  19. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/errors.py +0 -0
  20. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp/py.typed +0 -0
  21. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp_connect_python.egg-info/SOURCES.txt +0 -0
  22. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp_connect_python.egg-info/dependency_links.txt +0 -0
  23. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp_connect_python.egg-info/entry_points.txt +0 -0
  24. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp_connect_python.egg-info/requires.txt +0 -0
  25. {slmp_connect_python-1.0.0 → slmp_connect_python-1.1.0}/slmp_connect_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slmp-connect-python
3
- Version: 1.0.0
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.0.0"
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"
@@ -10,7 +10,7 @@ The primary user-facing entry points are:
10
10
  - ``poll``
11
11
  """
12
12
 
13
- __version__ = "0.8.0"
13
+ __version__ = "1.0.1"
14
14
 
15
15
  from .async_client import AsyncSlmpClient
16
16
  from .client import SlmpClient
@@ -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 _parse_device_for_family(device: str | DeviceRef, address_profile: object | None) -> DeviceRef:
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 = _parse_device_for_family(device, address_profile)
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 = _parse_device_for_family(device, address_profile)
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 = _parse_device_for_family(device, address_profile)
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 = _parse_device_for_family(dev, address_profile)
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 = _parse_device_for_family(dev, address_profile)
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 = [_parse_device_for_family(device, address_profile) for device in word_devices]
503
- dwords = [_parse_device_for_family(device, address_profile) for device in dword_devices]
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 = _parse_device_for_family(device, address_profile)
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 = _parse_device_for_family(device, address_profile)
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 = _parse_device_for_family(device, address_profile)
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 = _parse_device_for_family(device, address_profile)
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 family.
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().lower()
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