Skip to content

Latest commit

 

History

History
245 lines (188 loc) · 7.71 KB

File metadata and controls

245 lines (188 loc) · 7.71 KB

AGENTS.md - Python S7Comm Project Guidelines

Project Overview

python-s7comm is an unofficial Python implementation of the Siemens S7 communication protocol, enabling communication with Siemens S7 PLCs (Programmable Logic Controllers). The library provides both synchronous and asynchronous clients at two abstraction levels.

Architecture

Three-Layer Architecture

  1. High-Level Clients (python_s7comm/sync_client.py, async_client.py)

    • Client - Synchronous high-level API with string-based addresses
    • AsyncClient - Asynchronous high-level API with string-based addresses
    • Provides convenience methods:
      • connect(), disconnect(), close()
      • read_area(), write_area()
      • read_multi_vars(), write_multi_vars()
      • get_cpu_state(), get_order_code()
      • read_szl(), read_szl_list()
      • plc_stop()
  2. Core S7Comm (python_s7comm/s7comm/client.py, async_client.py)

    • S7Comm - Synchronous core implementation
    • AsyncS7Comm - Asynchronous core implementation
    • BaseS7Comm - Shared base class with common logic
    • Handles PDU reference management, packet validation, userdata continuation
  3. Transport Layer (python_s7comm/s7comm/transport/)

    • COTP / AsyncCOTP - Connection-Oriented Transport Protocol
    • TPKT - ISO Transport Service on top of TCP
    • Transport / AsyncTransport - Protocol interfaces

Core Components

  • python_s7comm/ - Main package
    • sync_client.py - High-level synchronous Client class
    • async_client.py - High-level asynchronous AsyncClient class
    • s7comm/ - Core S7 protocol implementation
      • client.py - Core S7Comm class
      • async_client.py - Core AsyncS7Comm class
      • base.py - BaseS7Comm shared logic
      • enums.py - Protocol enumerations
      • szl.py - System Status List (SZL) parsing
      • exceptions.py - S7Comm exceptions
      • error_messages.py - Error code mappings
      • packets/ - S7 protocol packet definitions
      • transport/ - Transport layer (COTP, TPKT)

Protocol Stack

High-Level API:    Client/AsyncClient (python_s7comm.sync_client/async_client)
         ↓
Core S7Comm:       S7Comm/AsyncS7Comm (python_s7comm.s7comm.client/async_client)
         ↓
Transport Layer:   COTP/AsyncCOTP (python_s7comm.s7comm.transport.cotp)
         ↓
Network Layer:     TPKT (python_s7comm.s7comm.transport.cotp.tpkt)
         ↓
TCP/IP Socket

Code Style & Conventions

General Rules

  • Python 3.12+ required (tested on 3.12 and 3.13)
  • Line length: 120 characters max (configured in ruff)
  • Formatting: Use ruff format
  • Linting: Use ruff for linting
  • Type checking: Use mypy (strict mode)
  • Type hints: Required for all function signatures

Naming Conventions

  • Classes: PascalCase (e.g., S7Comm, VariableAddress, SetupCommunicationRequest)
  • Functions/methods: snake_case (e.g., read_area, serialize_parameter)
  • Constants: UPPER_SNAKE_CASE (e.g., MAX_VARS, PROTOCOL_ID)
  • Enums: PascalCase class names, UPPER_SNAKE_CASE members

Code Patterns

  • Use @dataclass for data structures (see VariableAddress)
  • Use Protocol classes for interfaces (see Transport, S7Packet)
  • Use struct module for binary serialization/deserialization
  • Implement serialize() and parse() methods for protocol packets
  • Use @classmethod for factory/parse methods

Comments

  • Add docstrings for public API methods
  • Keep inline comments minimal and meaningful
  • All comments and documentation must be in English

Testing

Test Framework

  • pytest with pytest-asyncio for async tests
  • pytest-cov for coverage
  • asyncio_mode = "auto" configured in pyproject.toml

Test Structure

tests/
├── test_*.py           # Unit tests (no hardware required)
├── s1200/              # Protocol unit tests (pcap-based, no hardware)
│   └── test_*.py       # Tests using captured packet data
└── real_plc_s1200/     # Integration tests (requires PLC or simulator)
    ├── conftest.py     # Fixtures with PLC connection
    ├── data_items.py   # Test data definitions
    └── test_*.py       # Integration test files

Running Tests

# Unit tests only
uv run pytest tests/test_*.py

# All tests (requires PLC/simulator)
uv run pytest

# With coverage
uv run pytest --cov=python_s7comm

Test Environment

Integration tests require environment variables or .env file:

PLC_IP=127.0.0.1
PLC_RACK=0
PLC_SLOT=1
PLC_PORT=1102

Test Patterns

  • Use @pytest.mark.parametrize for multiple test cases
  • Use fixtures from conftest.py for client connections
  • Test both serialization and parsing (round-trip)
  • Include edge cases and invalid input tests

Development Setup

# Install with dev dependencies
uv sync --group dev --group test

# Install pre-commit hooks
uv run pre-commit install

# Run pre-commit on all files
uv run pre-commit run --all-files

Pre-commit Hooks

Configured hooks (.pre-commit-config.yaml):

  • trailing-whitespace, end-of-file-fixer
  • check-ast, check-json, check-toml, check-yaml
  • mypy for type checking
  • ruff for linting
  • ruff-format for formatting

Key Enums Reference

Area Types (Area)

  • P (0x80) - Peripheral
  • I (0x81) - Inputs
  • Q (0x82) - Outputs
  • M (0x83) - Markers
  • DB (0x84) - Data blocks
  • C (0x1C) - Counters
  • T (0x1D) - Timers

Data Types (DataType)

  • BOOL, BYTE, CHAR, WORD, INT, DWORD, DINT, REAL, COUNTER, TIMER

Message Types (MessageType)

  • JobRequest (0x01) - Request sent by the master
  • Ack (0x02) - Simple acknowledgement with no data
  • Response (0x03) - Ack-Data response
  • Userdata (0x07) - Extended protocol for SZL, CPU functions
  • ServerControl (0x08) - Server control request

Function Codes (FunctionCode)

  • SetupCommunication (0xF0)
  • ReadVariable (0x04)
  • WriteVariable (0x05)
  • PLCControl (0x28)
  • PLCStop (0x29)

Exceptions

  • S7CommException - Base exception
  • ReadVariableException - Read operation failure
  • PacketLostError - PDU reference mismatch (lost packet)
  • StalePacketError - Received old packet (retry receive)

Address Format

DB area:     DB<db_number>.<start>[.<bit>] <data_type> <amount>
Other areas: <area><start>[.<bit>] <data_type> <amount>

Examples:
  DB1.100 BYTE 10      # Read 10 bytes from DB1 at offset 100
  DB5.0.0 BOOL 1       # Read bit 0 from DB5 offset 0
  M0 INT 5             # Read 5 INTs from Marker area
  I0.0 BOOL 1          # Read input bit

Important Implementation Notes

  1. PDU Reference: Auto-incremented 16-bit counter for request/response matching
  2. PDU Length: Negotiated during connection setup (default 480 bytes, max depends on PLC model)
  3. Max Variables: 20 items per multi-read/write operation (MAX_VARS)
  4. Byte Order: Big-endian (! in struct format) for protocol, some little-endian for PDU reference
  5. Protocol ID: Always 0x32 for S7comm

Common Tasks

Adding New Packet Type

  1. Create packet class in python_s7comm/s7comm/packets/
  2. Implement S7Packet protocol (serialize_parameter, serialize_data)
  3. Add parse() classmethod for deserialization
  4. Register in S7PacketParser.parse() method
  5. Add unit tests

Adding New Data Type

  1. Add to relevant enums in enums.py
  2. Update VariableAddress regex patterns if needed
  3. Add tests in test_addresses.py

Release Process

  1. Update version in pyproject.toml
  2. Run all checks: uv run pre-commit run --all-files
  3. Run tests: uv run pytest tests/ --ignore=tests/real_plc_s1200/
  4. Create and push tag: git tag -a v0.0.1 -m "v0.0.1" && git push origin v0.0.1
  5. GitHub Actions will automatically build and publish to PyPI