Architecture Design
1. verview
ModbusLink adopts a modern layered architecture design, strictly following the Single Responsibility Principle and Open/Closed Principle to ensure code maintainability, extensibility, and testability.
2. Overall Architecture Diagram
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ User Code and Business Logic │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────┐
│ Client Layer │
│ ModbusClient, AsyncModbusClient │
│ • High-level API encapsulation • Data type conversion │
│ • Error handling │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────┐
│ Protocol Layer │
│ Modbus Protocol Implementation │
│ • PDU construction • Function code handling │
│ • Data validation │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────┐
│ Transport Layer │
│ TCP, RTU, ASCII Transport Implementation │
│ • Connection management • Data frame handling │
│ • Checksum calculation │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────┐
│ Physical Layer │
│ Ethernet / Serial / Other │
└─────────────────────────────────────────────────────────────┘
3. Core Design Principles
Layered Architecture - Each layer only interacts with adjacent layers - Upper layers depend on lower layers, not vice versa - Facilitates independent testing and replacement
Dependency Injection - Clients receive transport layer instances through constructors - Reduces coupling, improves testability
Interface-Oriented Programming - Uses abstract base classes to define interfaces - Concrete implementations are pluggable
Strategy Pattern - Different transport methods as different strategies - Runtime transport strategy switching
5. Transport Layer Design
5.1 Transport Architecture ~~~~~~~~~~———————-
SyncBaseTransport (ABC)
├── SyncTcpTransport
├── SyncRtuTransport
└── SyncAsciiTransport
AsyncBaseTransport (ABC)
├── AsyncTcpTransport
├── AsyncRtuTransport
└── AsyncAsciiTransport
5.2 Core Interfaces
Synchronous Transport Interface
class SyncBaseTransport(ABC):
@abstractmethod
def open(self) -> None:
"""Open transport connection"""
@abstractmethod
def close(self) -> None:
"""Close transport connection"""
@abstractmethod
def is_open(self) -> bool:
"""Check connection status"""
@abstractmethod
def flush(self) -> int:
"""Synchronously flush all pending data in receive buffer"""
@abstractmethod
def send_and_receive(self, slave_id: int, pdu: bytes, timeout: Optional[float] = None) -> bytes:
"""Send PDU and receive response"""
Asynchronous Transport Interface
class AsyncBaseTransport(ABC):
@abstractmethod
async def open(self) -> None:
"""Asynchronously open transport connection"""
@abstractmethod
async def close(self) -> None:
"""Asynchronously close transport connection"""
@abstractmethod
def is_open(self) -> bool:
"""Check connection status"""
@abstractmethod
async def flush(self) -> int:
"""Asynchronously flush all pending data in receive buffer"""
@abstractmethod
async def send_and_receive(self, slave_id: int, pdu: bytes, timeout: Optional[float] = None) -> bytes:
"""Asynchronously send PDU and receive response"""
5.3 Transport Implementation Details
TCP Transport (Modbus TCP)
MBAP Header (7 bytes) + PDU
┌──────┬──────┬──────┬──────┬──────────┬─────────────┐
│ TID │ PID │ Length │ Unit ID │ Function │
│ (2) │ (2) │ (2) │ (1) │ Code + Data │
└──────┴──────┴──────┴──────┴──────────┴─────────────┘
TID: Transaction Identifier for request-response matching
PID: Protocol Identifier, 0 for Modbus
Length: Length of following bytes
Unit ID: Unit identifier (slave address)
RTU Transport (Modbus RTU)
RTU Frame
┌─────────┬─────────────┬─────────────┐
│ Address │ Function │ CRC-16 │
│ (1) │ Code + Data │ (2) │
└─────────┴─────────────┴─────────────┘
Address: Slave address (1-247)
CRC-16: Cyclic redundancy check using Modbus polynomial
Frame interval: At least 3.5 character times
ASCII Transport (Modbus ASCII)
ASCII Frame
┌───┬─────────┬─────────────┬─────┬─────┬───┐
│ : │ Address │ Function │ LRC │ CR │ LF│
│ │ (2) │ Code + Data │ (2) │ │ │
└───┴─────────┴─────────────┴─────┴─────┴───┘
Start: Colon character ‘:’
LRC: Longitudinal redundancy check
End: Carriage return and line feed (CR LF)
6. Client Layer Design
6.1 Client Architecture
┌─────────────────────────────────────┐
│ High-level Data Type API │
│ read_float32, write_string, etc. │
└─────────────────────────────────────┘
│
┌─────────────────────────────────────┐
│ Standard Modbus API │
│ read_holding_registers, etc. │
└─────────────────────────────────────┘
│
┌─────────────────────────────────────┐
│ Protocol Processing Layer │
│ PDU construction, response parsing, exception handling │
└─────────────────────────────────────┘
│
┌─────────────────────────────────────┐
│ Transport Layer Interface │
│ BaseTransport/AsyncBaseTransport │
└─────────────────────────────────────┘
6.2 Design Patterns Application
Template Method Pattern
class SyncModbusClient:
def _execute_request(self, slave_id: int, function_code: int,
data: bytes) -> bytes:
"""Template method: defines request execution flow"""
# 1. Construct PDU
pdu = self._build_pdu(function_code, data)
# 2. Send and receive
response = self._transport.send_and_receive(pdu)
# 3. Validate response
self._validate_response(response, function_code)
# 4. Parse data
return self._parse_response(response)
Decorator Pattern
def connection_required(func):
"""Decorator to ensure connection exists"""
def wrapper(self, *args, **kwargs):
if not self._transport.is_connected():
raise ConnectionError("Not connected")
return func(self, *args, **kwargs)
return wrapper
7. Server Layer Design
7.1 Server Architecture
┌─────────────────────────────────────┐
│ Protocol Handler │
│ Request parsing, response construction, exception handling │
└─────────────────────────────────────┘
│
┌─────────────────────────────────────┐
│ Data Storage Layer │
│ Coil and register read/write operations │
└─────────────────────────────────────┘
│
┌─────────────────────────────────────┐
│ Transport Server │
│ TCP/RTU/ASCII server implementation │
└─────────────────────────────────────┘
7.2 Data Storage Design
class ModbusDataStore:
"""Thread-safe Modbus data storage"""
def __init__(self, coils_size: int = 65536,
discrete_inputs_size: int = 65536,
holding_registers_size: int = 65536,
input_registers_size: int = 65536):
self._coils = [False] * coils_size
self._discrete_inputs = [False] * discrete_inputs_size
self._holding_registers = [0] * holding_registers_size
self._input_registers = [0] * input_registers_size
self._lock = threading.RLock() # Reentrant lock
8. Utility Layer Design
8.1 Utility Components
CRC Calculator - Implements ModbusRTU standard CRC-16 algorithm - Supports lookup table optimization
LRC Calculator - Implements ModbusASCII standard LRC algorithm - Support verification data
Data Encoder - Big/little endian byte order conversion - Various data type encoding/decoding
9. Error Handling Architecture
9.1 Exception Hierarchy
ModbusLinkError (Base exception class) ├── CommunicationError (Base class for communication errors) │ ├── ConnectError (Connection error exception) │ └── TimeOutError (Timeout error exception) ├── ValidationError (Base class for data validation errors) │ ├── CrcError (CRC validation error exception) │ ├── LrcError (LRC validation error exception) │ └── InvalidReplyError (Invalid reply error exception) └── ModbusException (Protocol error)
9.2 Exception Handling Strategy
Transport Layer Exceptions: Network, serial communication errors
Protocol Layer Exceptions: Modbus protocol format errors
Application Layer Exceptions: Business logic errors
10. Performance Optimization Design
10.1 Connection Pool
class ModbusConnectionPool:
"""Modbus connection pool supporting connection reuse"""
def __init__(self, max_connections: int = 10):
self._pool = asyncio.Queue(maxsize=max_connections)
self._connections = set()
async def acquire(self) -> AsyncModbusClient:
"""Acquire connection"""
async def release(self, client: AsyncModbusClient):
"""Release connection"""
10.2 Batch Operation Optimization
async def batch_read_registers(self, requests: List[ReadRequest]) -> List[List[int]]:
"""Batch read registers, automatically optimized to minimize requests"""
# Merge consecutive address requests
optimized_requests = self._optimize_requests(requests)
# Concurrent execution
tasks = [self._read_registers(**req) for req in optimized_requests]
return await asyncio.gather(*tasks)
11. Extensibility Design
11.1 Plugin Architecture
class ModbusPlugin(ABC):
"""Modbus plugin base class"""
@abstractmethod
def on_request(self, request: SyncModbusRequest) -> SyncModbusRequest:
"""Request preprocessing"""
@abstractmethod
def on_response(self, response: SyncModbusResponse) -> SyncModbusResponse:
"""Response postprocessing"""
11.2 Custom Transport Layer
class WebSocketTransport(AsyncBaseTransport):
"""WebSocket transport layer example"""
async def connect(self):
self._websocket = await websockets.connect(self._uri)
async def send_and_receive(self, data: bytes) -> bytes:
await self._websocket.send(data)
response = await self._websocket.recv()
return response
12. Summary
ModbusLink’s architecture design offers the following advantages:
Clear layered structure with well-defined responsibilities for each layer
High extensibility supporting custom transport layers and protocol extensions
Excellent performance with async support and connection pooling
Comprehensive error handling providing detailed exception information
Good testability with each layer independently testable
This design ensures ModbusLink can meet current requirements while providing flexibility for future extensions.