Skip to content

Commit

Permalink
Client/Server framer as enum.
Browse files Browse the repository at this point in the history
  • Loading branch information
janiversen committed Oct 17, 2023
1 parent d3aa53d commit 515069b
Show file tree
Hide file tree
Showing 15 changed files with 148 additions and 118 deletions.
1 change: 1 addition & 0 deletions API_changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Versions (X.Y.Z) where Z > 0 e.g. 3.0.1 do NOT have API changes!

API changes 3.6.0 (future)
--------------------------
- framer= is an enum: pymodbus.Framer, but still accept a framer class


API changes 3.5.0
Expand Down
8 changes: 2 additions & 6 deletions examples/client_custom_msg.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,10 @@
import logging
import struct

from pymodbus import Framer
from pymodbus.bit_read_message import ReadCoilsRequest
from pymodbus.client import AsyncModbusTcpClient as ModbusClient

# --------------------------------------------------------------------------- #
# import the various server implementations
# --------------------------------------------------------------------------- #
from pymodbus.pdu import ModbusExceptions, ModbusRequest, ModbusResponse
from pymodbus.transaction import ModbusSocketFramer


# --------------------------------------------------------------------------- #
Expand Down Expand Up @@ -130,7 +126,7 @@ def __init__(self, address, **kwargs):

async def main(host="localhost", port=5020):
"""Run versions of read coil."""
with ModbusClient(host=host, port=port, framer=ModbusSocketFramer) as client:
with ModbusClient(host=host, port=port, framer_name=Framer.SOCKET) as client:
await client.connect()

# new modbus function code.
Expand Down
6 changes: 3 additions & 3 deletions examples/client_performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
import asyncio
import time

from pymodbus import Framer
from pymodbus.client import AsyncModbusSerialClient, ModbusSerialClient
from pymodbus.transaction import ModbusRtuFramer


LOOP_COUNT = 1000
Expand All @@ -29,7 +29,7 @@ def run_sync_client_test():
print("--- Testing sync client v3.4.1")
client = ModbusSerialClient(
"/dev/ttys007",
framer=ModbusRtuFramer,
framer_name=Framer.RTU,
baudrate=9600,
)
client.connect()
Expand All @@ -56,7 +56,7 @@ async def run_async_client_test():
print("--- Testing async client v3.4.1")
client = AsyncModbusSerialClient(
"/dev/ttys007",
framer=ModbusRtuFramer,
framer_name=Framer.RTU,
baudrate=9600,
)
await client.connect()
Expand Down
2 changes: 2 additions & 0 deletions pymodbus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
"""

__all__ = [
"Framer",
"pymodbus_apply_logging_config",
"__version__",
"__version_full__",
]

from pymodbus.framer import Framer
from pymodbus.logging import pymodbus_apply_logging_config


Expand Down
32 changes: 16 additions & 16 deletions pymodbus/client/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,20 @@
class ModbusBaseClient(ModbusClientMixin, ModbusProtocol):
"""**ModbusBaseClient**
**Parameters common to all clients**:
:param framer: (optional) Modbus Framer class.
:param timeout: (optional) Timeout for a request, in seconds.
:param retries: (optional) Max number of retries per request.
:param retry_on_empty: (optional) Retry on empty response.
:param close_comm_on_error: (optional) Close connection on error.
:param strict: (optional) Strict timing, 1.5 character between requests.
:param broadcast_enable: (optional) True to treat id 0 as broadcast address.
:param reconnect_delay: (optional) Minimum delay in milliseconds before reconnecting.
:param reconnect_delay_max: (optional) Maximum delay in milliseconds before reconnecting.
:param on_reconnect_callback: (optional) Function that will be called just before a reconnection attempt.
:param no_resend_on_retry: (optional) Do not resend request when retrying due to missing response.
:param kwargs: (optional) Experimental parameters.
Optional parameters:
:param framer: Modbus Framer class.
:param timeout: Timeout for a request, in seconds.
:param retries: Max number of retries per request.
:param retry_on_empty: Retry on empty response.
:param close_comm_on_error: Close connection on error.
:param strict: Strict timing, 1.5 character between requests.
:param broadcast_enable: True to treat id 0 as broadcast address.
:param reconnect_delay: Minimum delay in milliseconds before reconnecting.
:param reconnect_delay_max: Maximum delay in milliseconds before reconnecting.
:param on_reconnect_callback: Function that will be called just before a reconnection attempt.
:param no_resend_on_retry: Do not resend request when retrying due to missing response.
:param kwargs: Experimental parameters.
.. tip::
**reconnect_delay** doubles automatically with each unsuccessful connect, from
Expand Down Expand Up @@ -62,7 +62,7 @@ class _params:

def __init__( # pylint: disable=too-many-arguments
self,
framer: type[ModbusFramer] = None,
framer_class: type[ModbusFramer] = None,
timeout: float = 3,
retries: int = 3,
retry_on_empty: bool = False,
Expand Down Expand Up @@ -114,7 +114,7 @@ def __init__( # pylint: disable=too-many-arguments
self.slaves: list[int] = []

# Common variables.
self.framer = framer(ClientDecoder(), self)
self.framer = framer_class(ClientDecoder(), self)
self.transaction = DictTransactionManager(
self, retries=retries, retry_on_empty=retry_on_empty, **kwargs
)
Expand Down
22 changes: 12 additions & 10 deletions pymodbus/client/mixin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""Modbus Client Common."""
from __future__ import annotations

import struct
from enum import Enum
from typing import Any, List, Tuple, Union
from typing import Any

import pymodbus.bit_read_message as pdu_bit_read
import pymodbus.bit_write_message as pdu_bit_write
Expand Down Expand Up @@ -381,7 +383,7 @@ def diag_get_comm_event_log(self, **kwargs: Any) -> ModbusResponse:
def write_coils(
self,
address: int,
values: Union[List[bool], bool],
values: list[bool] | bool,
slave: int = 0,
**kwargs: Any,
) -> ModbusResponse:
Expand All @@ -398,7 +400,7 @@ def write_coils(
)

def write_registers(
self, address: int, values: Union[List[int], int], slave: int = 0, **kwargs: Any
self, address: int, values: list[int] | int, slave: int = 0, **kwargs: Any
) -> ModbusResponse:
"""Write registers (code 0x10).
Expand All @@ -423,7 +425,7 @@ def report_slave_id(self, slave: int = 0, **kwargs: Any) -> ModbusResponse:
"""
return self.execute(pdu_other_msg.ReportSlaveIdRequest(slave, **kwargs))

def read_file_record(self, records: List[Tuple], **kwargs: Any) -> ModbusResponse:
def read_file_record(self, records: list[tuple], **kwargs: Any) -> ModbusResponse:
"""Read file record (code 0x14).
:param records: List of (Reference type, File number, Record Number, Record Length)
Expand All @@ -432,7 +434,7 @@ def read_file_record(self, records: List[Tuple], **kwargs: Any) -> ModbusRespons
"""
return self.execute(pdu_file_msg.ReadFileRecordRequest(records, **kwargs))

def write_file_record(self, records: List[Tuple], **kwargs: Any) -> ModbusResponse:
def write_file_record(self, records: list[tuple], **kwargs: Any) -> ModbusResponse:
"""Write file record (code 0x15).
:param records: List of (Reference type, File number, Record Number, Record Length)
Expand Down Expand Up @@ -465,7 +467,7 @@ def readwrite_registers(
read_address: int = 0,
read_count: int = 0,
write_address: int = 0,
values: Union[List[int], int] = 0,
values: list[int] | int = 0,
slave: int = 0,
**kwargs,
) -> ModbusResponse:
Expand Down Expand Up @@ -534,8 +536,8 @@ class DATATYPE(Enum):

@classmethod
def convert_from_registers(
cls, registers: List[int], data_type: DATATYPE
) -> Union[int, float, str]:
cls, registers: list[int], data_type: DATATYPE
) -> int | float | str:
"""Convert registers to int/float/str.
:param registers: list of registers received from e.g. read_holding_registers()
Expand All @@ -558,8 +560,8 @@ def convert_from_registers(

@classmethod
def convert_to_registers(
cls, value: Union[int, float, str], data_type: DATATYPE
) -> List[int]:
cls, value: int | float | str, data_type: DATATYPE
) -> list[int]:
"""Convert int/float/str to registers (16/32/64 bit).
:param value: value to be converted
Expand Down
28 changes: 15 additions & 13 deletions pymodbus/client/serial.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
"""Modbus client async serial communication."""
from __future__ import annotations

import asyncio
import time
from contextlib import suppress
from functools import partial
from typing import Any, Type
from typing import Any

from pymodbus.client.base import ModbusBaseClient
from pymodbus.exceptions import ConnectionException
from pymodbus.framer import ModbusFramer
from pymodbus.framer.rtu_framer import ModbusRtuFramer
from pymodbus.framer import FRAMER_NAME_TO_CLASS, Framer
from pymodbus.logging import Log
from pymodbus.transport import CommType
from pymodbus.utilities import ModbusTransactionState
Expand All @@ -27,7 +28,6 @@ class AsyncModbusSerialClient(ModbusBaseClient, asyncio.Protocol):
Optional parameters:
:param framer: Framer class.
:param baudrate: Bits per second.
:param bytesize: Number of bits per byte 7-8.
:param parity: 'E'ven, 'O'dd or 'N'one
Expand All @@ -36,6 +36,7 @@ class AsyncModbusSerialClient(ModbusBaseClient, asyncio.Protocol):
Common optional parameters:
:param framer: Framer enum name
:param timeout: Timeout for a request, in seconds.
:param retries: Max number of retries per request.
:param retry_on_empty: Retry on empty response.
Expand Down Expand Up @@ -65,7 +66,7 @@ async def run():
def __init__(
self,
port: str,
framer: Type[ModbusFramer] = ModbusRtuFramer,
framer: Framer = Framer.RTU,
baudrate: int = 19200,
bytesize: int = 8,
parity: str = "N",
Expand All @@ -74,9 +75,10 @@ def __init__(
) -> None:
"""Initialize Asyncio Modbus Serial Client."""
asyncio.Protocol.__init__(self)
framer_class = FRAMER_NAME_TO_CLASS.get(framer, framer)
ModbusBaseClient.__init__(
self,
framer=framer,
framer_class=framer_class, # type: ignore[arg-type]
CommType=CommType.SERIAL,
host=port,
baudrate=baudrate,
Expand Down Expand Up @@ -106,7 +108,6 @@ class ModbusSerialClient(ModbusBaseClient):
Optional parameters:
:param framer: Framer class.
:param baudrate: Bits per second.
:param bytesize: Number of bits per byte 7-8.
:param parity: 'E'ven, 'O'dd or 'N'one
Expand All @@ -115,6 +116,7 @@ class ModbusSerialClient(ModbusBaseClient):
Common optional parameters:
:param framer: Framer enum name
:param timeout: Timeout for a request, in seconds.
:param retries: Max number of retries per request.
:param retry_on_empty: Retry on empty response.
Expand Down Expand Up @@ -150,7 +152,7 @@ def run():
def __init__(
self,
port: str,
framer: Type[ModbusFramer] = ModbusRtuFramer,
framer: Framer = Framer.RTU,
baudrate: int = 19200,
bytesize: int = 8,
parity: str = "N",
Expand All @@ -160,9 +162,10 @@ def __init__(
"""Initialize Modbus Serial Client."""
self.transport = None
kwargs["use_sync"] = True
framer_class = FRAMER_NAME_TO_CLASS.get(framer, framer)
ModbusBaseClient.__init__(
self,
framer=framer,
framer_class=framer_class, # type: ignore[arg-type]
CommType=CommType.SERIAL,
host=port,
baudrate=baudrate,
Expand Down Expand Up @@ -207,10 +210,9 @@ def connect(self): # pylint: disable=invalid-overridden-method
baudrate=self.comm_params.baudrate,
parity=self.comm_params.parity,
)
if isinstance(self.framer, ModbusRtuFramer):
if self.params.strict:
self.socket.interCharTimeout = self.inter_char_timeout
self.last_frame_end = None
if self.params.strict:
self.socket.interCharTimeout = self.inter_char_timeout
self.last_frame_end = None
except serial.SerialException as msg:
Log.error("{}", msg)
self.close()
Expand Down
Loading

0 comments on commit 515069b

Please sign in to comment.