diff --git a/doc/API/error.rst b/doc/API/error.rst new file mode 100644 index 00000000..49616390 --- /dev/null +++ b/doc/API/error.rst @@ -0,0 +1,5 @@ +Errors +====== + +.. automodule:: snap7.error + :members: diff --git a/doc/conf.py b/doc/conf.py index 0f83ae74..4ee00e33 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -7,7 +7,19 @@ # -- General configuration ----------------------------------------------------- -extensions = ["sphinx.ext.autodoc", "sphinx.ext.coverage", "sphinx.ext.viewcode", "sphinx.ext.napoleon"] +extensions = ["sphinx.ext.autodoc", "sphinx.ext.coverage", "sphinx.ext.viewcode", "sphinx.ext.napoleon", "sphinx.ext.intersphinx"] + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), +} + +nitpick_ignore = [ + ("py:class", "ctypes.Array"), + ("py:class", "ctypes.c_int"), + ("py:class", "ctypes.c_uint"), + ("py:class", "snap7.type.S7DataItem"), + ("py:class", "snap7.type.c_ubyte_Array_65536"), +] templates_path = ["_templates"] diff --git a/doc/index.rst b/doc/index.rst index 6086dee2..0c9b1f7a 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -48,6 +48,7 @@ Welcome to python-snap7's documentation! API/optimizer API/log API/type + API/error .. toctree:: :maxdepth: 2 diff --git a/s7commplus/async_client.py b/s7commplus/async_client.py index 37727c90..61187dc7 100644 --- a/s7commplus/async_client.py +++ b/s7commplus/async_client.py @@ -511,7 +511,7 @@ async def browse(self) -> list[dict[str, Any]]: .. warning:: This method is **experimental** and may change. Returns a flat list of variable dicts with keys ``name``, ``access_sequence`` - (the dot-separated hex LID path usable with :meth:`read_tag`), ``data_type``, + (the dot-separated hex LID path usable with ``read_tag()``), ``data_type``, and the optimized/non-optimized byte+bit offsets. Steps: enumerate DBs, resolve each DB's type-info RID via a LID=1 read, explore the OMS type-info container, then recombine into the symbol tree. diff --git a/s7commplus/client.py b/s7commplus/client.py index d91c5ff7..a8a7f230 100644 --- a/s7commplus/client.py +++ b/s7commplus/client.py @@ -394,7 +394,7 @@ def browse(self) -> list[dict[str, Any]]: .. warning:: This method is **experimental** and may change. Returns a flat list of variable dicts with keys ``name``, ``access_sequence`` - (the dot-separated hex LID path usable with :meth:`read_tag`), ``data_type``, + (the dot-separated hex LID path usable with ``read_tag()``), ``data_type``, and the optimized/non-optimized byte+bit offsets. Steps: enumerate DBs, resolve each DB's type-info RID via a LID=1 read, explore the OMS type-info container, then recombine into the symbol tree. @@ -477,7 +477,7 @@ def create_subscription(self, items: list[tuple[int, int, int]], cycle_ms: int = .. warning:: This method is **experimental** and may change. The PLC will push data updates for the specified variables. Use - :meth:`receive_notification` to receive the pushed data. + ``receive_notification()`` to receive the pushed data. Args: items: List of (db_number, start_offset, size) tuples to monitor. diff --git a/snap7/async_client.py b/snap7/async_client.py index fd4deb95..2038ec06 100644 --- a/snap7/async_client.py +++ b/snap7/async_client.py @@ -4,7 +4,7 @@ Uses asyncio streams for non-blocking I/O with an asyncio.Lock() to serialize send/receive cycles, ensuring safe concurrent use via asyncio.gather(). -For new projects, use :class:`s7.AsyncClient` instead, which supports all PLC +For new projects, use ``s7.AsyncClient`` instead, which supports all PLC models and automatically selects the best protocol. """ @@ -287,7 +287,7 @@ class AsyncClient(ClientMixin): serializes each send+receive cycle so that concurrent coroutines (e.g. via asyncio.gather) never interleave on the same TCP socket. - For new projects, use :class:`s7.AsyncClient` instead. + For new projects, use ``s7.AsyncClient`` instead. Examples: >>> from s7 import AsyncClient diff --git a/snap7/client.py b/snap7/client.py index 5c3ef6cf..7cc5ee32 100644 --- a/snap7/client.py +++ b/snap7/client.py @@ -2,7 +2,7 @@ Legacy S7 client implementation. Pure Python implementation of the classic S7 protocol. For new projects, -use :class:`s7.Client` instead, which supports all PLC models and +use ``s7.Client`` instead, which supports all PLC models and automatically selects the best protocol. """ @@ -247,7 +247,7 @@ class Client(ClientMixin): Legacy S7 client for classic PUT/GET communication. Supports S7-300, S7-400, S7-1200 and S7-1500 PLCs via the classic S7 - protocol. For new projects, use :class:`s7.Client` instead, which + protocol. For new projects, use ``s7.Client`` instead, which automatically selects the best protocol for any supported PLC. Examples: @@ -784,13 +784,13 @@ def db_write_array(self, db_number: int, start: int, values: list[Any], fmt: str return self.db_write(db_number, start, data) def read_tag(self, tag: "Union[Tag, str]") -> Any: - """Read a typed value by :class:`Tag` or address string. + """Read a typed value by :class:`~snap7.tags.Tag` or address string. Accepts a :class:`~snap7.tags.Tag` or a PLC4X-style address string (e.g. ``"DB1.DBX0.0:BOOL"``, ``"DB1:10:INT"``, ``"M10.5:BOOL"``). Args: - tag: A :class:`Tag` instance or a parseable address string. + tag: A :class:`~snap7.tags.Tag` instance or a parseable address string. Returns: The typed value (bool/int/float/datetime/str depending on type). @@ -811,10 +811,10 @@ def read_tag(self, tag: "Union[Tag, str]") -> Any: return _decode_tag(resolved, bytearray(data)) def write_tag(self, tag: "Union[Tag, str]", value: Any) -> int: - """Write a typed value by :class:`Tag` or address string. + """Write a typed value by :class:`~snap7.tags.Tag` or address string. Args: - tag: A :class:`Tag` instance or a parseable address string. + tag: A :class:`~snap7.tags.Tag` instance or a parseable address string. value: The value to write (type must match the tag's datatype). Returns: @@ -841,7 +841,7 @@ def read_tags(self, tags: "list[Union[Tag, str]]") -> list[Any]: reads into minimal PDU exchanges. Args: - tags: List of :class:`Tag` instances or address strings. + tags: List of :class:`~snap7.tags.Tag` instances or address strings. Returns: List of decoded values in the same order as input. diff --git a/snap7/s7protocol.py b/snap7/s7protocol.py index 96382f9a..0e992830 100644 --- a/snap7/s7protocol.py +++ b/snap7/s7protocol.py @@ -136,8 +136,8 @@ def validate_pdu_reference(self, response_sequence: int) -> None: response_sequence: Sequence number from the response PDU. Raises: - S7StalePacketError: If response is older than expected (stale). - S7PacketLostError: If response is ahead of expected (packet loss). + ~snap7.error.S7StalePacketError: If response is older than expected (stale). + ~snap7.error.S7PacketLostError: If response is ahead of expected (packet loss). """ if response_sequence < self.sequence: raise S7StalePacketError(f"Stale packet: expected sequence {self.sequence}, got {response_sequence}") @@ -239,7 +239,7 @@ def extract_multi_read_data(self, response: Dict[str, Any], block_count: int) -> List of bytearrays, one per block. Raises: - S7ProtocolError: If any item has a non-success return code. + ~snap7.error.S7ProtocolError: If any item has a non-success return code. """ raw = response.get("raw_data", b"") if not raw: @@ -429,7 +429,7 @@ def check_control_response(self, response: Dict[str, Any]) -> None: response: Parsed S7 response Raises: - S7ProtocolError: If control operation failed + ~snap7.error.S7ProtocolError: If control operation failed """ # For now, just check that we got a response # In a full implementation, we would check specific error codes @@ -1048,7 +1048,7 @@ def parse_get_block_info_response(self, response: Dict[str, Any]) -> Dict[str, A return result def parse_list_blocks(self, response: Dict[str, Any]) -> BlocksList: - """Parse list blocks response directly into a :class:`BlocksList`. + """Parse list blocks response directly into a :class:`~snap7.type.BlocksList`. Consolidates the dict→struct conversion that used to live in both the sync and async clients so the field mapping is declared once. @@ -1056,7 +1056,7 @@ def parse_list_blocks(self, response: Dict[str, Any]) -> BlocksList: return build_blocks_list_from_dict(self.parse_list_blocks_response(response)) def parse_get_block_info(self, response: Dict[str, Any]) -> TS7BlockInfo: - """Parse block info response directly into a :class:`TS7BlockInfo`. + """Parse block info response directly into a :class:`~snap7.type.TS7BlockInfo`. Consolidates the dict→struct conversion that used to live in both the sync and async clients. @@ -1602,7 +1602,7 @@ def check_write_response(self, response: Dict[str, Any]) -> None: response: Parsed S7 response Raises: - S7ProtocolError: If write operation failed + ~snap7.error.S7ProtocolError: If write operation failed """ # First check for errors in the response header # S7-1200/1500 returns error codes in the header for write failures @@ -1631,7 +1631,7 @@ def check_write_response(self, response: Dict[str, Any]) -> None: def build_blocks_list_from_dict(counts: Dict[str, int]) -> BlocksList: - """Populate a :class:`BlocksList` from the dict returned by ``parse_list_blocks_response``.""" + """Populate a :class:`~snap7.type.BlocksList` from the dict returned by ``parse_list_blocks_response``.""" block_list = BlocksList() block_list.OBCount = counts.get("OBCount", 0) block_list.FBCount = counts.get("FBCount", 0) @@ -1644,7 +1644,7 @@ def build_blocks_list_from_dict(counts: Dict[str, int]) -> BlocksList: def build_block_info_from_dict(info: Dict[str, Any]) -> TS7BlockInfo: - """Populate a :class:`TS7BlockInfo` from the dict returned by ``parse_get_block_info_response``.""" + """Populate a :class:`~snap7.type.TS7BlockInfo` from the dict returned by ``parse_get_block_info_response``.""" block_info = TS7BlockInfo() block_info.BlkType = info["block_type"] block_info.BlkNumber = info["block_number"] diff --git a/snap7/server/__init__.py b/snap7/server/__init__.py index 248c8bc0..4ff9b519 100644 --- a/snap7/server/__init__.py +++ b/snap7/server/__init__.py @@ -2,7 +2,7 @@ Legacy S7 server implementation. Provides a complete S7 server emulator for the classic S7 protocol. For new -projects, use :class:`s7.Server` instead, which supports both legacy S7 and +projects, use ``s7.Server`` instead, which supports both legacy S7 and S7CommPlus clients. """ @@ -46,7 +46,7 @@ class Server: Legacy S7 server implementation. Emulates a Siemens S7 PLC for testing and development purposes. - For new projects, use :class:`s7.Server` instead. + For new projects, use ``s7.Server`` instead. Examples: >>> from s7 import Server diff --git a/snap7/tags.py b/snap7/tags.py index bcd17bd2..efe1775a 100644 --- a/snap7/tags.py +++ b/snap7/tags.py @@ -714,7 +714,7 @@ def load_json(source: Union[str, Path]) -> dict[str, Tag]: def from_browse(variables: list[dict[str, Any]]) -> dict[str, Tag]: - """Build a dict of Tags from :meth:`s7.Client.browse` results. + """Build a dict of Tags from ``Client.browse()`` results. .. warning:: This function is **experimental** and may change. diff --git a/snap7/util/setters.py b/snap7/util/setters.py index 038e17f7..836a575b 100644 --- a/snap7/util/setters.py +++ b/snap7/util/setters.py @@ -170,8 +170,8 @@ def set_fstring(bytearray_: Buffer, byte_index: int, value: str, max_length: int Raises: :obj:`TypeError`: if the `value` is not a :obj:`str`. - :obj:`ValueError`: if the length of the `value` is larger than the `max_size` - or 'value' contains non-ascii characters. + :obj:`ValueError`: if the length of the `value` is larger than the + ``max_size`` or ``value`` contains non-ASCII characters. Examples: >>> data = bytearray(20) @@ -208,8 +208,9 @@ def set_string(bytearray_: Buffer, byte_index: int, value: str, max_size: int = Raises: :obj:`TypeError`: if the `value` is not a :obj:`str`. - :obj:`ValueError`: if the length of the `value` is larger than the `max_size` - or 'max_size' is greater than 254 or 'value' contains ascii characters > 255. + :obj:`ValueError`: if the length of the `value` is larger than the + ``max_size``, or ``max_size`` is greater than 254, or ``value`` + contains characters with ordinal > 255. Examples: >>> from snap7.util import set_string