Skip to content

Commit d4a7324

Browse files
committed
Update mixed block retry handling
1 parent 9e32184 commit d4a7324

5 files changed

Lines changed: 29 additions & 24 deletions

File tree

TODO.md

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,22 @@ This file tracks the remaining tasks and unresolved issues for the SLMP Python l
66

77
- [ ] **Extended Specification live coverage expansion**: The capture-aligned implementation is working on validated paths, but broader
88
address-range, transport, and PLC-family coverage is still open. QnUDV has no
9-
`HG`; `U0\G10` read-only on the current QnUDV target returned `0xC070` with
10-
command `0x0401` subcommand `0x0080`.
11-
12-
- [ ] **Mixed block write root cause**: The practical fallback is implemented, but the reason some validated PLC
13-
paths reject the first one-request mixed `1406` write with `0xC05B` is still
14-
not fully explained. On the current QnUDV target, word-only, bit-only, and
15-
mixed `1406` block writes returned `0xC059`, so this appears to be block-write
16-
command support rather than a mixed-only rejection on that target.
9+
`HG`; QnUDV `U0\G10` read-only was live-checked on 2026-05-15 against
10+
`Q06UDVCPU` and returned `[0]` across Python, Node-RED, .NET, Rust, and C++
11+
Minimal. QCPU `U0\G10` read-only was
12+
live-checked on 2026-05-15 against `Q12HCPU` and returned `[0]` across
13+
Python, Node-RED, .NET, Rust, and C++ Minimal. QnU `U0\G10` read-only was
14+
live-checked on 2026-05-15 against `Q26UDEHCPU` and returned `[0]` across the
15+
same five stacks.
16+
17+
- [x] **Mixed block write root cause**: The old `0xC05B` note is no longer
18+
tracked as a live unresolved PLC behavior; it was likely from an in-progress
19+
library/payload implementation. Current QCPU/QnU/QnUDV live checks across
20+
Python, Node-RED, .NET, Rust, and C++ Minimal consistently show: word-only
21+
block read/write for `D9000` succeeds, bit-only block read/write for `Y1FFF`
22+
returns `0x4031`, mixed word+bit block read returns `0x4031`, and mixed
23+
word+bit block write returns `0xC056`. Mixed block access is rejected because
24+
the bit-block part is rejected.
1725

1826
## 2. Testing & Validation
1927

@@ -26,7 +34,10 @@ This file tracks the remaining tasks and unresolved issues for the SLMP Python l
2634
- `mx-r` -> `4e/iqr` with range family `mx-r`
2735

2836
## 3. Known Issues
29-
- [ ] **Single-request mixed block write (`1406`)**: Single-request mixed block write (`1406`) has not yet been accepted on any current live-verified PLC path in this project. Prefer `split_mixed_blocks=True` for the safest operational behavior, or use `retry_mixed_on_error=True` if you still want to probe the one-request form first.
37+
- [x] **Single-request mixed block write (`1406`)**: Current live checks show
38+
single-request mixed block write is rejected on the checked QCPU/QnU/QnUDV
39+
targets because the bit-block part is rejected. This is documented behavior
40+
and not a remaining Python defect.
3041
- [x] **ASCII mode out of scope**: ASCII mode is intentionally out of scope for this project. Binary 3E/4E is the only planned data-code path unless a concrete compatibility requirement appears.
3142
- [x] **Raw wrappers internal-only**: `*_raw` wrappers are for library developers and maintainers. Keep them documented only in internal maintainer materials; they are not a user-facing roadmap item.
3243
- [ ] **Extended Specification broader validation**: Extended Specification access for `G/HG` is not stable across all series. The iQ-R `_ext` builder now matches the captured `U3E0\G10` and `U3E0\HG20` payload shape, the dedicated coverage command can sweep multiple transports and named targets, and live checks now confirm: `TCP + SELF/SELF-CPU1 + U3E0\\G10/U3E0\\HG20 + points=1/4` for read-only coverage; target-aligned write/readback/restore for `SELF-CPU2/U3E1`, `SELF-CPU3/U3E2`, and `SELF-CPU4/U3E3` at both `points=1` and `points=4`, first on `G10/HG20`, then on `G30/HG30`, then on `G50/HG50` with restoration back to the original non-zero `G50` values, and now on `G70/HG70` and `G90/HG90`; aligned `UDP/1027` read-only and write/readback/restore for `SELF/U3E0`, `SELF-CPU1/U3E0`, and `SELF-CPU2..4/U3E1..3` on `G10/HG20` at `points=1` and `points=4`; non-aligned `points=1` write failures with the stable pattern `G -> 0x414A`, `HG -> readback_mismatch`; and `UDP/1025` timeouts for the earlier `SELF/SELF-CPU1` read-only sweep. Broader validation beyond those address ranges and broader UDP address coverage is still pending. Use CPU buffer access commands unless you have validated the exact Extended Specification path on the actual PLC.

internal_docs/maintainer/PROTOCOL_SPEC.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ Random bit write (`1402`) state value:
155155
Block read/write compatibility handling:
156156
- `read_block` and `write_block` send mixed word+bit blocks in one `0406/1406` request by default, matching the manual.
157157
- If a target environment needs a compatibility fallback, `split_mixed_blocks=True` sends two commands (word-only, bit-only).
158-
- For write-side compatibility, `retry_mixed_on_error=True` first sends one mixed `1406` request and retries as split word-only and bit-only writes only when the PLC returns a known mixed-write rejection end code. The current retry set is `0xC056`, `0xC05B`, and `0xC061`.
158+
- For write-side compatibility, `retry_mixed_on_error=True` first sends one mixed `1406` request and retries as split word-only and bit-only writes only when the PLC returns a known mixed-write rejection end code. The current retry set is `0xC056` and `0xC061`; `0xC05B` is preserved as an observed PLC end code but is not a retry trigger.
159159
- Practical recommendation on current hardware evidence: if mixed write reliability matters more than keeping the manual's one-request form, prefer `split_mixed_blocks=True`.
160160
- As of 2026-03-19, this project has no live-verified PLC path where the first one-request mixed `1406` write was accepted.
161161
- `bit_blocks` does not use one value per bit.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "slmp-connect-python"
7-
version = "0.1.14"
7+
version = "0.1.15"
88
description = "SLMP Connect Python: client library for Mitsubishi SLMP binary communication"
99
readme = "README.md"
1010
requires-python = ">=3.10"

slmp/core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1271,7 +1271,7 @@ def _warn_boundary_behavior(
12711271
_G_HG_CODES = frozenset({"G", "HG"})
12721272
_TEMPORARILY_UNSUPPORTED_TYPED_CODES = frozenset({"G", "HG"})
12731273
_BOUNDARY_START_ACCEPTANCE_CODES = frozenset({"R", "ZR"})
1274-
_MIXED_BLOCK_RETRY_END_CODES = frozenset({0xC056, 0xC05B, 0xC061})
1274+
_MIXED_BLOCK_RETRY_END_CODES = frozenset({0xC056, 0xC061})
12751275

12761276

12771277
def _encode_label_name(label: str) -> bytes:

tests/test_slmp.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2125,25 +2125,19 @@ def test_write_block_default_keeps_mixed_request(self) -> None:
21252125
self.assertEqual(len(client.requests), 1)
21262126
self.assertEqual(client.requests[0][0], Command.DEVICE_WRITE_BLOCK)
21272127

2128-
def test_write_block_retry_mixed_on_c05b_splits_after_failed_combined_request(self) -> None:
2129-
"""Test test_write_block_retry_mixed_on_c05b_splits_after_failed_combined_request."""
2128+
def test_write_block_retry_mixed_on_c05b_does_not_split(self) -> None:
2129+
"""Test test_write_block_retry_mixed_on_c05b_does_not_split."""
21302130
client = FakeClient()
2131-
client.response_queue = [(0xC05B, b""), (0x0000, b""), (0x0000, b"")]
2132-
with warnings.catch_warnings(record=True) as caught:
2133-
warnings.simplefilter("always")
2131+
client.response_queue = [(0xC05B, b"")]
2132+
with self.assertRaises(SlmpError) as ctx:
21342133
client.write_block(
21352134
word_blocks=[("D100", [0x1111])],
21362135
bit_blocks=[("M200", [0x0001])],
21372136
series=PLCSeries.IQR,
21382137
retry_mixed_on_error=True,
21392138
)
2140-
self.assertEqual(len(client.requests), 3)
2141-
self.assertEqual([request[0] for request in client.requests], [Command.DEVICE_WRITE_BLOCK] * 3)
2142-
self.assertEqual([request[2][:2] for request in client.requests], [b"\x01\x01", b"\x01\x00", b"\x00\x01"])
2143-
self.assertTrue(all(request[3]["raise_on_error"] is False for request in client.requests))
2144-
self.assertTrue(
2145-
any(isinstance(item.message, SlmpPracticalPathWarning) and "0xC05B" in str(item.message) for item in caught)
2146-
)
2139+
self.assertEqual(ctx.exception.end_code, 0xC05B)
2140+
self.assertEqual(len(client.requests), 1)
21472141

21482142
def test_write_block_retry_mixed_on_c056_splits_after_failed_combined_request(self) -> None:
21492143
"""Test test_write_block_retry_mixed_on_c056_splits_after_failed_combined_request."""

0 commit comments

Comments
 (0)