Conversation
Extract PLC memory bank configuration, address parsing, and validation logic from ClickNick into standalone pyclickplc modules. Introduces BankConfig frozen dataclass, parse_address strict parser, and simplified AddressRecord model. 93 tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract shared code from ClickNick into pyclickplc: - blocks.py: BlockTag/BlockRange parsing and multi-row block operations - dataview.py: DataviewRow model, CDV file I/O, storage/display conversion - nicknames.py: CSV read/write for address data using AddressRecord Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ModbusMapping dataclass, MODBUS_MAPPINGS for all 16 banks, forward/reverse address mapping (with sparse coil and XD/YD stride-2 support), and struct-based pack/unpack for register values. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… simulator) with full CLICK PLC address space support. - client.py: ClickClient with AddressAccessor bank accessors, AddressInterface for string-based access, TagInterface for nickname-based access, TXT 2-chars-per-register handling, sparse X/Y coil support - server.py: DataProvider protocol, MemoryDataProvider (in-memory backend), _ClickDeviceContext (custom pymodbus datastore with reverse mapping, FC 06 read-modify-write for width-2 types, SC/SD writability enforcement) - modbus.py: Add modbus_to_plc_register() extended reverse mapping (returns reg_position for mid-value registers) - 142 new tests (559 total): unit tests for client/server + integration round-trips for all data types
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Delete display-style parse_address, rename parse_address_display to parse_address (strict, raises ValueError). The entire Modbus layer now uses MDB indices, making XD/YD registers contiguous (base + index) and eliminating stride-2 special-case branches. Key fixes: - XD0u is now addressable (MDB index 1, Modbus register 57345) - _reverse_register and modbus_to_plc_register use generic 0-based logic - Server and client delegate formatting to format_address_display - T coil base corrected from 45057 to 45056 (0-based Modbus convention) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace storage_to_display/display_to_storage with four functions: storage_to_datatype, datatype_to_storage (CDV strings <-> native Python types) and datatype_to_display, display_to_datatype (native types <-> UI strings). This moves formatting concerns (hex padding, float precision, char display) out of the storage layer. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All read() calls now return ModbusResponse (a Mapping) with canonical uppercase keys (DS1, X001) and case-insensitive look-ups. Adds AddressAccessor.__getitem__ for `await plc.ds[1]` bare-value shorthand. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
`TypeCode` was only used for CDV storage encoding/decoding and did not need to remain public. - Renamed `TypeCode` to `_CdvStorageCode` in `src/pyclickplc/dataview.py` - Updated all internal CDV conversion/mapping references and docstrings - Removed `TypeCode` from `src/pyclickplc/__init__.py` imports and `__all__` - Updated dataview/value tests to use `_CdvStorageCode` (with local alias for readability) - Verified with `make test` (`668 passed`) BREAKING CHANGE: `pyclickplc.TypeCode` and `pyclickplc.dataview.TypeCode` are no longer available; CDV storage codes are now internal.
Parameterize ModbusResponse and AddressAccessor with value type generics in client.py. Add bank Literal type aliases plus overloads for _get_accessor and __getattr__ to improve typed accessor inference (bool/int/float/str by bank). Keep runtime behavior unchanged while adding explicit casts in read paths where decoding is type-dependent. Refactor test_client.py with typed async-mock setup/access helpers and float narrowing helpers. Update frozen-dataclass tests in test_addresses.py, test_banks.py, and test_modbus.py to use cast(Any, obj).field = ..., preserving runtime immutability checks while avoiding ty invalid-assignment errors. Add minor typing-safe assertions in test_dataview.py and test_server.py. Verification run: make lint passed; targeted pytest modules for these test changes passed.
…aProvider - add shared `assert_runtime_value()` validation helper keyed by `DataType` - apply strict value checks in client write path (type + range/format + float32 finite/packable) - validate values in `MemoryDataProvider.write()` so `set()` and `bulk_set()` inherit same behavior - keep SC/SD writability enforcement in Modbus server flow unchanged - add coverage for client and provider rejection cases (overflow, bool-as-int, NaN/Inf, TXT length/ASCII) - update CLICK device/server specs to document strict runtime validation contract and WORD semantics
Add shared CDV verification helpers in pyclickplc.dataview: check_cdv_file(path) check_cdv_files(project_path) Reuse existing CDV parse/conversion logic for range, type-code, and writability checks. Add coverage for new CDV check helpers in test_dataview.py. Reduce pyclickplc.__all__ / top-level re-exports to a curated public surface. Intentionally do not expose TypeCode; keep CDV type constants internal (_CdvStorageCode).
…eneration - Rewrite `README.md` to be core-user focused: - Keep install + quickstart - Cover `ClickClient`, `ClickServer`/`MemoryDataProvider`, CSV/CDV I/O, and address helpers - Remove validation-helper and advanced API sections from primary README narrative - Keep `read_mdb_csv` out of README - Add MkDocs docs stack with generated API reference: - Add `mkdocs.yml` with Material theme, `mkdocstrings`, and `gen-files` plugin - Add `docs/gen_reference.py` to auto-generate module reference pages from `src/pyclickplc` - Add starter docs pages under `docs/` (`index` + core usage guides) - Add docs tooling: - Add `docs` dependency group in `pyproject.toml` (`mkdocs`, `mkdocs-material`, `mkdocstrings[python]`, `mkdocs-gen-files`) - Add `make docs-serve`, `make docs-build`, and `make docs-check` - Verification: - `mkdocs build --strict` passes with generated API pages in nav.
**Description**
`TXT` values are packed as 2 chars per Modbus register (little-endian byte layout), but reverse register mapping treated `TXT` as linear 1:1 register-to-address. This caused high-index writes like `TXT101` to land on `TXT51`/`TXT52` in server/provider routing.
This change fixes TXT mapping consistency across forward/reverse paths:
- `plc_to_modbus("TXT", index)` now maps via `(index - 1) // 2`
- reverse register mapping (`modbus_to_plc`, `modbus_to_plc_register`) now returns the odd TXT base index for each packed register pair
Added regressions to cover:
- forward mapping (`TXT2` shares register with `TXT1`, `TXT1000` address)
- reverse mapping (`36865 -> TXT3`, `37363 -> TXT999`)
- server context read/write at high TXT indices (`TXT101`/`TXT102`)
This restores correct server-side TXT routing for packed character registers.
## Hardware Capability Profile
`ClickHardwareProfile` provides table-driven ladder portability rules:
- bank/address writability (`is_writable`)
- fixed instruction-role compatibility (`valid_for_role`)
- copy-family bank compatibility (`copy_compatible`)
- compare compatibility (`compare_compatible`, `compare_constant_compatible`)
```python
from pyclickplc import CLICK_HARDWARE_PROFILE
CLICK_HARDWARE_PROFILE.is_writable("SC", 50) # True
CLICK_HARDWARE_PROFILE.valid_for_role("T", "timer_done_bit") # True
CLICK_HARDWARE_PROFILE.copy_compatible("single", "X", "Y") # True
```
Description: Removed read_mdb_csv from pyclickplc and pyclickplc.nicknames. Removed export_cdv, get_dataview_folder, list_cdv_files from pyclickplc and pyclickplc.dataview. Made dataview folder/file discovery internal (_get_dataview_folder, _list_cdv_files) for check_cdv_files. Updated tests/docs/changelog to reflect the breaking API surface change. Footer: BREAKING CHANGE: read_mdb_csv, export_cdv, get_dataview_folder, and list_cdv_files are no longer public in pyclickplc.
remove check_cdv_files, _get_dataview_folder, _list_cdv_files from dataview.py remove check_cdv_files from package exports in __init__.py remove related tests and update CHANGELOG.md note
ty check was failing due to overly broad str inference in literal-typed compatibility data and test parameters. Added _constant_kinds(*kinds: CompareConstantKind) -> frozenset[CompareConstantKind] in capabilities.py and used it for COMPARE_CONSTANT_COMPATIBILITY values, so they type-check as CompareConstantKind instead of str. Updated test_capabilities.py to use literal type aliases (InstructionRole, CopyOperation, CompareConstantKind) in parametrized test function signatures. Lint/type pipeline now passes (make lint).
Description: - replace `DataviewRow.type_code` with `data_type: DataType | None` - add `DataviewRow.update_data_type()` and clear `data_type` in `clear()` - rename `get_type_code_for_address()` to `get_data_type_for_address()` - remove type-code memory maps and use `banks.MEMORY_TYPE_TO_DATA_TYPE` - keep CDV integer storage codes only at file boundaries via: - `_CDV_CODE_TO_DATA_TYPE` - `_DATA_TYPE_TO_CDV_CODE` - update conversion functions to dispatch on `DataType`: - `storage_to_datatype` - `datatype_to_storage` - `datatype_to_display` - `display_to_datatype` - add New Value helpers: - `DataviewRow.new_value_display` - `DataviewRow.set_new_value_from_display()` - `validate_new_value()` - `DataviewRow.validate_new_value()` - update `check_cdv_file()` and `_validate_cdv_new_value()` to use `DataType` - update `tests/test_dataview.py` for `DataType` + new API coverage - export only `get_data_type_for_address` and `validate_new_value` at package root; keep conversion helpers out of `__all__` Verification: - `make lint` passes - `make test` passes
- Refactor DataviewRow.new_value to native Python types with None as unset - Add DataviewFile dataclass to own CDV path/header/has_new_values/rows and provide load/save/verify instance workflows - Add read_cdv/write_cdv/verify_cdv APIs and DataviewFile display/validation helpers - Move storage<->native conversion to file boundary logic - Implement native-value verification with type-aware normalization - Preserve byte-identical output for unchanged read->save round trips save_cdv from pyclickplc.dataview and package exports - Update docs/examples to use read_cdv/write_cdv - Update dataview tests for native new_value semantics and new API surface
Introduce a pyclickplc-owned ModbusService for synchronous/UI callers. - Add `ModbusService`, `ConnectionState`, and `WriteResult` in `src/pyclickplc/modbus_service.py` - Implement background thread + asyncio loop bridge with connect/disconnect lifecycle - Add replace-style poll configuration (`set_poll_addresses`, `clear_poll_addresses`, `stop_polling`) and periodic `on_values` callback emission - Add synchronous `read` with canonical normalized address keys and ClickClient-style error semantics (`ValueError` for invalid addresses, `OSError` for transport failures) - Add synchronous `write` accepting mapping or iterable inputs and returning per-address outcomes for partial success/error reporting - Implement deterministic read/write batching with sparse bank and width-2 bank handling, respecting Modbus request size limits - Export new public API from `src/pyclickplc/__init__.py` - Add docs: README updates, `docs/guides/modbus_service.md`, docs nav/index links - Add `tests/test_modbus_service.py` covering lifecycle, polling semantics, read/write behavior, batching heuristics, and thread-safety Validation: `make lint`, `uv run ty check src tests`, and `make test` all pass.
…allback deadlocks ModbusService now performs a full shutdown on disconnect/close by stopping and joining its background asyncio thread, instead of only disconnecting the client. Also added a fail-fast guard for sync API calls made from on_state/on_values callbacks (service thread), so these now raise RuntimeError instead of deadlocking. Includes: - loop thread lifecycle management (start/ensure/stop) and reconnect-after-disconnect support - coroutine cleanup on early submit failures to avoid unawaited-coroutine warnings - close() alias for disconnect() - tests for thread shutdown, reconnect behavior, and callback re-entrancy protection - docs update in guides/modbus_service.md for shutdown semantics and callback usage rules
… behavior - Add ClickServer runtime/client APIs: - is_running() - list_clients() - disconnect_client() - disconnect_all_clients() - Add ServerClientInfo dataclass for client id + peer display. - Add run_server_tui(...) helper with commands: help, status, clients, disconnect <id>, disconnect all, shutdown/exit/quit. - Export new APIs from package root and document TUI usage in README + server guide. - Change ClickClient defaults to disable implicit auto-reconnect (reconnect_delay=0.0, reconnect_delay_max=0.0), with explicit opt-in params. - Add regression/integration tests covering: - server runtime controls - TUI command loop behavior - disconnect_all keeping client disconnected (no silent reconnect)
…nsitions - add ReconnectConfig and ModbusService(reconnect=...) for friendly auto-reconnect setup - pass reconnect_delay/reconnect_delay_max through to ClickClient (default remains disabled) - emit on_state(ERROR, err) when poll reads start failing, not every failed cycle - emit CONNECTED after poll read recovery so later failures trigger ERROR again - add/extend tests and docs for reconnect wiring and poll failure behavior
…d shared AddressNormalizerMixin and apply it to ModbusResponse and MemoryDataProvider for consistent canonical address handling. Introduce AddressRecordMap as the read_csv() return type (dict[int, AddressRecord]-compatible) with: - records.addr[...] normalized address lookup - records.tag[...] case-insensitive nickname lookup - lazy index rebuild with mutation invalidation Enforce nickname.lower() collision rejection during CSV load while preserving existing int-key access and write_csv content-only behavior. Refactor ClickClient tags to programmatic input: - remove tag_filepath - add tags: Mapping[str, AddressRecord] | None - build tags from AddressRecord.nickname - auto-skip empty nicknames - reject case-insensitive collisions - resolve tag read/write names case-insensitively Hard-rename DataviewRow -> DataViewRecord across code, tests, and exports. Update README/docs/changelog and extend tests for new lookup/tag behavior, collision handling, and normalization parity. Validation: - uv run pytest -q (703 passed) - uv run ruff check . - uv run ty check"
Align ClickClient XD/YD ergonomics with display indexing and explicit upper-byte aliases. - Make `plc.xd` / `plc.yd` display-indexed (`0..8`) instead of MDB-indexed (`0..16`) - Add `plc.xdu` / `plc.ydu` fixed aliases for `XD0u` / `YD0u` - Update `AddressInterface` XD/YD ranges to display-step semantics (e.g. `XD0-XD8`) - Reject XD/YD range endpoints with `u` suffix; keep single-address `XD0u` / `YD0u` support - Preserve low-level MDB semantics in address/modbus helper APIs - Add/adjust client tests and README examples for new behavior BREAKING CHANGE: `ClickClient.xd` and `ClickClient.yd` now use display indexing (`0..8`), so calls like `plc.xd[3]` now resolve to `XD3` (MDB 6), not MDB index 3.
Allow leading underscores for PLC system-generated nicknames (SC, SD, X banks). Adds SYSTEM_NICKNAME_TYPES constant so consumers like ClickNick can use it directly instead of maintaining their own workarounds. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allow leading underscores and forbidden characters for PLC system-generated nicknames (SC, SD, X banks). Length and reserved keyword checks still apply. Adds SYSTEM_NICKNAME_TYPES constant so consumers like ClickNick can use it directly instead of maintaining their own workarounds. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts: # src/pyclickplc/validation.py # tests/test_validation.py
…ings Remove 12 non-existent capability exports from __all__, drop phantom capabilities module and internal modules (banks, modbus, blocks) from mkdocs nav, add __init__ docstrings to ClickClient/ClickServer/ModbusService, and fix uv install → uv add in README. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Bump classifier to Beta, add Python 3.14, remove pyrung entry point - Clear changelog for initial release - tag.read() now requires a tag name; tag.read_all(include_system=False) reads all tags, skipping SC/SD system banks by default - README: split tag example, clarify ModbusResponse, drop unpublished docs link Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…me helpers ## Summary Add non-breaking structured block-name helpers while preserving `BlockTag.name` as an opaque round-trippable string. ## What changed - Added `StructuredBlockName` in `pyclickplc.blocks` - Added `parse_structured_block_name(name)` (opt-in, non-throwing parser) - Added `group_udt_block_names(names)` to group UDT fields by base name (`Base.field`) - Kept core block-tag parsing/matching logic unchanged (`parse_block_tag`, pairing, range matching) ## Spec/Test updates - Added tests to verify `BlockTag.name` round-trips unchanged for: - `Base.field` - `Base:named_array(2,3)` - `Base:block(5)` - `Base:block(start=5)` - Added tests for helper parser behavior and UDT grouping ## Why This gives downstream tools (for example ClickNick) an opt-in way to interpret structured names for UI features (like grouping UDT attributes) without changing existing block-tag semantics.
…it system_bank rules This changes nickname validation from a broad boolean flag to explicit bank-based rules. ## What changed - Removed `is_system` from `validate_nickname(...)`. - `validate_nickname` now accepts only: - `system_bank="SC"` or `"SD"` for SC/SD system nickname rules - `system_bank="X"` for strict X system nickname rules (`_IO<number>...` only) - `system_bank=None` for normal user nickname rules ## Behavior now - `system_bank=None`: - rejects leading `_` - enforces forbidden character checks - `system_bank="SC"` / `"SD"`: - allows PLC system punctuation and leading underscores - `system_bank="X"`: - requires `_IO<number>...` - still enforces forbidden character checks This removes ambiguous “system mode” behavior and forces callers to pass explicit context when they want system-style validation. ## Tests Updated validation tests to reflect the new API and stricter bank-specific behavior.
Replace test-only assignment suppressions with explicit typed casts. - In tests/test_server.py, replace six `# type: ignore[assignment]` comments with `cast(ModbusTcpServer, ...)` assignments for `_server`. - Add required imports for `cast` and `ModbusTcpServer`. - Keep formatter/lint cleanup from Ruff in blocks/client test files (import normalization, line wrap, and blank-line cleanup). Result: `make lint` passes cleanly.
Merge doc rewrite
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Includes
dev2dev2Notes
mainwas placeholder-only before this PR.v0.1.0