Skip to content

Latest commit

 

History

History
123 lines (97 loc) · 6.15 KB

CLAUDE.md

File metadata and controls

123 lines (97 loc) · 6.15 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this is

pyads is a Python wrapper around Beckhoff's TwinCAT ADS protocol. It talks to the native ADS library via ctypes: on Linux/macOS that's libAdsLib.so (compiled from the adslib git submodule, https://github.com/Beckhoff/ADS), on Windows it's the vendor TcAdsDll.dll from a TwinCAT installation. Python code lives in src/pyads/; tests live in tests/.

Setup

The adslib submodule must be checked out and compiled before the package can be imported — import pyads will raise OSError: Failed to locate 'AdsLib.so' otherwise.

git submodule update --init --recursive   # if not cloned with --recursive
pip install -e .[tests,dev]                # triggers the native build, see below

pip install . (and editable installs) run a custom build_py step (see setup.py) that on Unix does the equivalent of:

cd adslib && meson setup build && ninja -C build && cd ..

...then copies the resulting libAdsLib.so to src/AdsLib.so. If you rebuild the submodule manually (e.g. cd adslib && make), re-run pip install -e . afterwards so the .so gets copied/renamed into src/ where pyads_ex.py looks for it — a stray adslib.so or libadslib.so in src/ without the exact name AdsLib.so will not be found.

On Windows, set TWINCAT3DIR if you need pyads to locate TcAdsDll.dll outside of PATH.

Common commands

# Run the full test suite (uses the fake in-process ADS test server, no PLC needed)
pytest

# Run a single test file / class / test
pytest tests/test_ads.py
pytest tests/test_ads.py::AdsTest::test_AmsAddr

# Run across all supported Python versions via tox
tox
tox -e py312

# Lint (runs mypy, then flake8, then pydocstyle against the pyads package)
python lint.py

# Format
black src/pyads

# Build the docs (Sphinx)
cd doc && make html   # output in doc/build/html

Notes:

  • Tests are plain unittest.TestCase classes, run through pytest. tests/conftest.py enables faulthandler because bugs in the native library can otherwise cause silent segfaults.
  • On Windows, a running TwinCAT router can interfere with the test server on the ADS port; either disable the TwinCAT runtime (TcSwitchRuntime.exe) or run tests in Docker/WSL instead (see CONTRIBUTING.md and Dockerfile).
  • black, flake8, and mypy are configured to exclude adslib/ and other build artifacts; flake8 config is in .flake8 (not in pyproject.toml, since flake8 doesn't support that).
  • Docstrings follow Sphinx's info-field-lists style (:param:, :type:, :return:, :rtype:) so API docs can be autogenerated — see CONTRIBUTING.md for the exact format.
  • Add an entry under ## [Unreleased] in CHANGELOG.md for any user-facing change.

Architecture

Layered from raw C bindings up to the public API:

  • pyads_ex.py — lowest layer. Loads the native ADS library via ctypes (with platform-specific logic for Windows/Linux/macOS/FreeBSD DLL discovery) and wraps the raw C API functions (adsPortOpenEx, adsSyncReadReqEx2, adsSyncAddDeviceNotificationReqEx, ...) with Python-friendly signatures and error checking (raises ADSError).
  • ads.py — the pythonic, function-based API built on top of pyads_ex: open_port, close_port, add_route, plus structure (DUT) marshalling helpers size_of_structure, dict_from_bytes, bytes_from_dict that convert between PLC struct byte layouts and Python dicts using a StructureDef tuple description.
  • connection.py — Connection, the main high-level OOP entry point most users interact with. Wraps ads.py functions, manages the AMS port/address lifecycle, and exposes read/write/read_by_name/write_by_name/notifications as methods with context-manager support (plc.open() / with plc:).
  • symbol.py — AdsSymbol, representing a named PLC variable. Resolves and caches type info from the PLC symbol table for typed reads/writes and notification callbacks without needing to specify the PLC type manually each time.
  • structs.py — ctypes.Structure definitions mirroring the ADS C structs (SAmsAddr, SAmsNetId, SAdsNotificationAttrib, SAdsSymbolEntry, ...) plus higher-level Python wrappers (AmsAddr, NotificationAttrib) used throughout the rest of the package.
  • constants.py — ADS protocol constants (index groups/offsets, PORT_*, ADSSTATE_*, ADSTRANS_*) and the PLCTYPE_* ctypes type aliases plus the DATATYPE_MAP / ads_type_to_ctype mapping between PLC datatypes and Python/ctypes types.
  • errorcodes.py / errorcodes_nc.py — ADS and NC error code → human-readable message tables consumed by ADSError.
  • filetimes.py — Windows FILETIME ⇄ Python datetime conversions, used for DATE/DT/TOD PLC types.
  • utils.py — small platform-detection and string helpers shared across the modules above.
  • testserver/ — a pure-Python fake ADS TCP server (AdsTestServer) that speaks enough of the ADS wire protocol to exercise pyads without a real PLC/TwinCAT device; this is what the entire test suite runs against. handler.py defines the AbstractHandler protocol and AMS packet dataclasses; basic_handler.py and advanced_handler.py implement two different response strategies; testserver.py runs the threaded socket server itself. It is also shipped as a public feature (pyads.testserver) for users who want to test their own code against a fake PLC.

__init__.py defines the public API surface — re-exports from the modules above are curated there rather than done with wildcard imports, so adding a new public symbol means adding an explicit import in __init__.py.

Git Conventions

  • NEVER include "Co-Authored-By" or "Generated with Claude" in commit messages.

The adslib submodule

adslib/ is a separate C++ project (Beckhoff's open-source AdsLib) vendored as a git submodule and built with meson/ninja (or make, per its own Makefile). pyads only depends on its compiled shared library output; there's normally no need to edit code inside adslib/ unless fixing a bug in the native layer itself.