Skip to content

Commit 7f9e6a0

Browse files
committed
Merge iQ-L range catalog support
2 parents 430d041 + f7511bb commit 7f9e6a0

10 files changed

Lines changed: 26 additions & 17 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ For normal application code:
7878
| --- | --- | --- | --- | --- | --- |
7979
| `iq-f` | `3e` | `ql` | octal | `iq-f` | live-validated |
8080
| `iq-r` | `4e` | `iqr` | hexadecimal | `iq-r` | live-validated |
81-
| `iq-l` | `4e` | `iqr` | hexadecimal | `iq-r` | live-validated on `L16HCPU` |
81+
| `iq-l` | `4e` | `iqr` | hexadecimal | `iq-l` | live-validated on `L16HCPU` |
8282
| `mx-f` | `4e` | `iqr` | hexadecimal | `mx-f` | provisional; review in `TODO.md` |
8383
| `mx-r` | `4e` | `iqr` | hexadecimal | `mx-r` | provisional; review in `TODO.md` |
8484
| `qcpu` | `3e` | `ql` | hexadecimal | `qcpu` | retained path |
@@ -94,7 +94,7 @@ High-level accepted `plc_family` values:
9494
| --- | --- | --- |
9595
| `iq-f` | FX5 / iQ-F | `X` / `Y` use manual octal text |
9696
| `iq-r` | iQ-R | `X` / `Y` use hexadecimal text |
97-
| `iq-l` | iQ-L | mapped to the `iq-r` range rules; live-validated on `L16HCPU` |
97+
| `iq-l` | iQ-L | independent iQ-L range rules; live-validated on `L16HCPU` |
9898
| `mx-f` | MX-F | pending live validation |
9999
| `mx-r` | MX-R | pending live validation |
100100
| `qcpu` | QCPU | `3e/ql` fixed profile |

TODO.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@ This file tracks the remaining tasks and unresolved issues for the SLMP Python l
44

55
## 1. Protocol Implementation Gaps
66

7-
- **`G/HG` Extended Specification live coverage expansion**
7+
- **Extended Specification live coverage expansion**
88
The capture-aligned implementation is working on validated paths, but broader
9-
address-range, transport, and PLC-family coverage is still open.
9+
address-range, transport, and PLC-family coverage is still open. QnUDV has no
10+
`HG`; `U0\G10` read-only on the current QnUDV target returned `0xC070` with
11+
command `0x0401` subcommand `0x0080`.
1012

1113
- **Mixed block write root cause**
1214
The practical fallback is implemented, but the reason some validated PLC
1315
paths reject the first one-request mixed `1406` write with `0xC05B` is still
14-
not fully explained.
15-
16-
- **`1617` Clear Error operator-visible effect**
17-
Transport-level acceptance is confirmed, but the operator-visible behavior on
18-
real hardware still needs better evidence.
16+
not fully explained. On the current QnUDV target, word-only, bit-only, and
17+
mixed `1406` block writes returned `0xC059`, so this appears to be block-write
18+
command support rather than a mixed-only rejection on that target.
1919

2020
## 2. Testing & Validation
2121

@@ -35,8 +35,10 @@ This file tracks the remaining tasks and unresolved issues for the SLMP Python l
3535

3636
## 4. Cross-Stack API Alignment
3737

38+
- [x] **Validate iQ-F X/Y octal handling on FX5 hardware**: FX5UC-32MT/D returned `X0000-X1777` and `Y0000-Y1777` as `Base8`; `X100` and `Y100` read successfully through iQ-F octal address parsing.
39+
- [x] **Split iQ-L from iQ-R range rules**: `iq-l` now resolves to its own range family while keeping 4E/iQR communication and iQ-R-style address parsing. `L16HCPU` was live-validated with `SM0-SM4095`, `SD0-SD4095`, `D0-D18431`, `LZ0-LZ1`, `LTN0-LTN1023`, `LSTN0-LSTN31`, and `LCN0-LCN511`.
40+
- [x] **Resolve Q-series runtime device ranges**: QCPU/LCPU/QnU/QnUDV `ZR` ranges are selected by probing readable addresses, `R` follows the probed `ZR` count capped at `R32767`, QCPU `Z` is selected by probing `Z15`, and LCPU/QnU/QnUDV `Z` is fixed at 20 points.
3841
- [ ] **Keep helper naming aligned with the managed stacks**: Preserve the shared high-level contract around `open_and_connect`, `read_typed`, `write_typed`, `write_bit_in_word`, `read_named`, and `poll`.
3942
- [ ] **Review public address helper exposure**: Decide whether the address parse/normalize/format helpers should be elevated into an explicit public utility API so applications do not need private string-parsing copies.
4043
- [ ] **Keep `plc_family` as the only high-level PLC selector**: Raw `frame_type`, access-profile, and range-family knobs should stay low-level only unless new live evidence forces a public exception.
4144
- [ ] **Preserve semantic atomicity by default**: Do not silently split reads or writes that callers would reasonably treat as one logical value or one logical block. Protocol-defined boundaries are acceptable, but fallback retries that change semantics should be opt-in and explicitly named.
42-
- [ ] **Preserve semantic atomicity by default**: Do not silently split reads or writes that callers would reasonably treat as one logical value or one logical block. Protocol-defined boundaries are acceptable, but fallback retries that change semantics should be opt-in and explicitly named.

docsrc/user/GETTING_STARTED.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Current fixed mapping:
5454
| --- | --- | --- | --- | --- |
5555
| `iq-f` | `3e` | `ql` | octal | `iq-f` |
5656
| `iq-r` | `4e` | `iqr` | hexadecimal | `iq-r` |
57-
| `iq-l` | `4e` | `iqr` | hexadecimal | `iq-r` |
57+
| `iq-l` | `4e` | `iqr` | hexadecimal | `iq-l` |
5858
| `mx-f` | `4e` | `iqr` | hexadecimal | `mx-f` |
5959
| `mx-r` | `4e` | `iqr` | hexadecimal | `mx-r` |
6060
| `qcpu` | `3e` | `ql` | hexadecimal | `qcpu` |

docsrc/user/USER_GUIDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ For the recommended high-level helper layer, `plc_family` is the only PLC select
9595
| --- | --- | --- | --- | --- | --- |
9696
| `iq-f` | `3e` | `ql` | octal | `iq-f` | live-validated |
9797
| `iq-r` | `4e` | `iqr` | hexadecimal | `iq-r` | live-validated |
98-
| `iq-l` | `4e` | `iqr` | hexadecimal | `iq-r` | live-validated on `L16HCPU` |
98+
| `iq-l` | `4e` | `iqr` | hexadecimal | `iq-l` | live-validated on `L16HCPU` |
9999
| `mx-f` | `4e` | `iqr` | hexadecimal | `mx-f` | provisional |
100100
| `mx-r` | `4e` | `iqr` | hexadecimal | `mx-r` | provisional |
101101
| `qcpu` | `3e` | `ql` | hexadecimal | `qcpu` | retained path |

slmp/core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class _PlcFamilyDefaults:
135135
_PLC_FAMILY_DEFAULTS: dict[str, _PlcFamilyDefaults] = {
136136
"iq-f": _PlcFamilyDefaults(FrameType.FRAME_3E, PLCSeries.QL, "iq-f", "iq-f"),
137137
"iq-r": _PlcFamilyDefaults(FrameType.FRAME_4E, PLCSeries.IQR, "iq-r", "iq-r"),
138-
"iq-l": _PlcFamilyDefaults(FrameType.FRAME_4E, PLCSeries.IQR, "iq-r", "iq-r"),
138+
"iq-l": _PlcFamilyDefaults(FrameType.FRAME_4E, PLCSeries.IQR, "iq-r", "iq-l"),
139139
"mx-f": _PlcFamilyDefaults(FrameType.FRAME_4E, PLCSeries.IQR, "mx-f", "mx-f"),
140140
"mx-r": _PlcFamilyDefaults(FrameType.FRAME_4E, PLCSeries.IQR, "mx-r", "mx-r"),
141141
"qcpu": _PlcFamilyDefaults(FrameType.FRAME_3E, PLCSeries.QL, "qcpu", "qcpu"),

slmp/device_ranges.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class SlmpDeviceRangeFamily(str, Enum):
1515
"""Explicit PLC family used to read device-range configuration."""
1616

1717
IqR = "iq-r"
18+
IqL = "iq-l"
1819
MxF = "mx-f"
1920
MxR = "mx-r"
2021
IqF = "iq-f"
@@ -493,6 +494,11 @@ def _undefined(notes: str) -> _RangeValueSpec:
493494
),
494495
}
495496

497+
_PROFILES[SlmpDeviceRangeFamily.IqL] = replace(
498+
_PROFILES[SlmpDeviceRangeFamily.IqR],
499+
family=SlmpDeviceRangeFamily.IqL,
500+
)
501+
496502

497503
def normalize_device_range_family(value: SlmpDeviceRangeFamily | str) -> SlmpDeviceRangeFamily:
498504
"""Normalize one canonical family identifier."""
@@ -512,6 +518,7 @@ def family_label(family: SlmpDeviceRangeFamily | str) -> str:
512518
normalized = normalize_device_range_family(family)
513519
return {
514520
SlmpDeviceRangeFamily.IqR: "IQ-R",
521+
SlmpDeviceRangeFamily.IqL: "iQ-L",
515522
SlmpDeviceRangeFamily.MxF: "MX-F",
516523
SlmpDeviceRangeFamily.MxR: "MX-R",
517524
SlmpDeviceRangeFamily.IqF: "IQ-F",

tests/test_async_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ def test_async_client_plc_family_derives_fixed_profile_defaults() -> None:
225225
assert cli.plc_series == PLCSeries.IQR
226226
assert cli.frame_type.value == "4e"
227227
assert cli.device_family == "iq-r"
228-
assert cli.device_range_family == "iq-r"
228+
assert cli.device_range_family == "iq-l"
229229

230230

231231
@pytest.mark.asyncio

tests/test_device_ranges.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def test_read_device_range_catalog_uses_client_plc_family_defaults(self) -> None
140140

141141
catalog = client.read_device_range_catalog()
142142

143-
self.assertEqual(catalog.family, SlmpDeviceRangeFamily.IqR)
143+
self.assertEqual(catalog.family, SlmpDeviceRangeFamily.IqL)
144144
self.assertEqual(client.plc_family, "iq-l")
145145
self.assertEqual(
146146
client.last_request,

tests/test_slmp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1792,7 +1792,7 @@ def test_plc_family_derives_fixed_profile_defaults(self) -> None:
17921792
self.assertEqual(client.plc_series, PLCSeries.IQR)
17931793
self.assertEqual(client.frame_type, FrameType.FRAME_4E)
17941794
self.assertEqual(client.device_family, "iq-r")
1795-
self.assertEqual(client.device_range_family, "iq-r")
1795+
self.assertEqual(client.device_range_family, "iq-l")
17961796

17971797
def test_plc_family_rejects_manual_profile_override(self) -> None:
17981798
with self.assertRaisesRegex(ValueError, "plc_family already determines"):

tests/test_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ def test_connection_options_derive_fixed_profile_from_plc_family(self):
580580
self.assertEqual(options.plc_series.value, "iqr")
581581
self.assertEqual(options.frame_type.value, "4e")
582582
self.assertEqual(options.device_family, "iq-r")
583-
self.assertEqual(options.device_range_family, "iq-r")
583+
self.assertEqual(options.device_range_family, "iq-l")
584584

585585

586586
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)