initial
This commit is contained in:
19
env/lib/python3.11/site-packages/pymodbus/client/__init__.py
vendored
Normal file
19
env/lib/python3.11/site-packages/pymodbus/client/__init__.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
"""Client"""
|
||||
|
||||
__all__ = [
|
||||
"AsyncModbusSerialClient",
|
||||
"AsyncModbusTcpClient",
|
||||
"AsyncModbusTlsClient",
|
||||
"AsyncModbusUdpClient",
|
||||
"ModbusBaseClient",
|
||||
"ModbusSerialClient",
|
||||
"ModbusTcpClient",
|
||||
"ModbusTlsClient",
|
||||
"ModbusUdpClient",
|
||||
]
|
||||
|
||||
from pymodbus.client.base import ModbusBaseClient
|
||||
from pymodbus.client.serial import AsyncModbusSerialClient, ModbusSerialClient
|
||||
from pymodbus.client.tcp import AsyncModbusTcpClient, ModbusTcpClient
|
||||
from pymodbus.client.tls import AsyncModbusTlsClient, ModbusTlsClient
|
||||
from pymodbus.client.udp import AsyncModbusUdpClient, ModbusUdpClient
|
||||
BIN
env/lib/python3.11/site-packages/pymodbus/client/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/pymodbus/client/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/pymodbus/client/__pycache__/base.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/pymodbus/client/__pycache__/base.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/pymodbus/client/__pycache__/mixin.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/pymodbus/client/__pycache__/mixin.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/pymodbus/client/__pycache__/serial.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/pymodbus/client/__pycache__/serial.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/pymodbus/client/__pycache__/tcp.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/pymodbus/client/__pycache__/tcp.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/pymodbus/client/__pycache__/tls.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/pymodbus/client/__pycache__/tls.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/pymodbus/client/__pycache__/udp.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/pymodbus/client/__pycache__/udp.cpython-311.pyc
vendored
Normal file
Binary file not shown.
324
env/lib/python3.11/site-packages/pymodbus/client/base.py
vendored
Normal file
324
env/lib/python3.11/site-packages/pymodbus/client/base.py
vendored
Normal file
@@ -0,0 +1,324 @@
|
||||
"""Base for all clients."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import socket
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable
|
||||
|
||||
from pymodbus.client.mixin import ModbusClientMixin
|
||||
from pymodbus.exceptions import ConnectionException, ModbusIOException
|
||||
from pymodbus.factory import ClientDecoder
|
||||
from pymodbus.framer import ModbusFramer
|
||||
from pymodbus.logging import Log
|
||||
from pymodbus.pdu import ModbusRequest, ModbusResponse
|
||||
from pymodbus.transaction import DictTransactionManager
|
||||
from pymodbus.transport import CommParams, ModbusProtocol
|
||||
from pymodbus.utilities import ModbusTransactionState
|
||||
|
||||
|
||||
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.
|
||||
|
||||
.. tip::
|
||||
Common parameters and all external methods for all clients are documented here,
|
||||
and not repeated with each client.
|
||||
|
||||
.. tip::
|
||||
**reconnect_delay** doubles automatically with each unsuccessful connect, from
|
||||
**reconnect_delay** to **reconnect_delay_max**.
|
||||
Set `reconnect_delay=0` to avoid automatic reconnection.
|
||||
|
||||
:mod:`ModbusBaseClient` is normally not referenced outside :mod:`pymodbus`.
|
||||
|
||||
**Application methods, common to all clients**:
|
||||
"""
|
||||
|
||||
@dataclass
|
||||
class _params:
|
||||
"""Parameter class."""
|
||||
|
||||
retries: int = None
|
||||
retry_on_empty: bool = None
|
||||
close_comm_on_error: bool = None
|
||||
strict: bool = None
|
||||
broadcast_enable: bool = None
|
||||
reconnect_delay: int = None
|
||||
|
||||
source_address: tuple[str, int] = None
|
||||
|
||||
server_hostname: str = None
|
||||
|
||||
def __init__( # pylint: disable=too-many-arguments
|
||||
self,
|
||||
framer: type[ModbusFramer] = None,
|
||||
timeout: float = 3,
|
||||
retries: int = 3,
|
||||
retry_on_empty: bool = False,
|
||||
close_comm_on_error: bool = False,
|
||||
strict: bool = True,
|
||||
broadcast_enable: bool = False,
|
||||
reconnect_delay: float = 0.1,
|
||||
reconnect_delay_max: float = 300,
|
||||
on_reconnect_callback: Callable[[], None] | None = None,
|
||||
no_resend_on_retry: bool = False,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize a client instance."""
|
||||
ModbusClientMixin.__init__(self)
|
||||
self.use_sync = kwargs.get("use_sync", False)
|
||||
setup_params = CommParams(
|
||||
comm_type=kwargs.get("CommType"),
|
||||
comm_name="comm",
|
||||
source_address=kwargs.get("source_address", ("0.0.0.0", 0)),
|
||||
reconnect_delay=reconnect_delay,
|
||||
reconnect_delay_max=reconnect_delay_max,
|
||||
timeout_connect=timeout,
|
||||
host=kwargs.get("host", None),
|
||||
port=kwargs.get("port", 0),
|
||||
sslctx=kwargs.get("sslctx", None),
|
||||
baudrate=kwargs.get("baudrate", None),
|
||||
bytesize=kwargs.get("bytesize", None),
|
||||
parity=kwargs.get("parity", None),
|
||||
stopbits=kwargs.get("stopbits", None),
|
||||
handle_local_echo=kwargs.get("handle_local_echo", False),
|
||||
)
|
||||
if not self.use_sync:
|
||||
ModbusProtocol.__init__(
|
||||
self,
|
||||
setup_params,
|
||||
False,
|
||||
)
|
||||
else:
|
||||
self.comm_params = setup_params
|
||||
self.params = self._params()
|
||||
self.params.retries = int(retries)
|
||||
self.params.retry_on_empty = bool(retry_on_empty)
|
||||
self.params.close_comm_on_error = bool(close_comm_on_error)
|
||||
self.params.strict = bool(strict)
|
||||
self.params.broadcast_enable = bool(broadcast_enable)
|
||||
self.on_reconnect_callback = on_reconnect_callback
|
||||
self.retry_on_empty: int = 0
|
||||
self.no_resend_on_retry = no_resend_on_retry
|
||||
self.slaves: list[int] = []
|
||||
|
||||
# Common variables.
|
||||
self.framer = framer(ClientDecoder(), self)
|
||||
self.transaction = DictTransactionManager(
|
||||
self, retries=retries, retry_on_empty=retry_on_empty, **kwargs
|
||||
)
|
||||
self.reconnect_delay_current = self.params.reconnect_delay
|
||||
self.use_udp = False
|
||||
self.state = ModbusTransactionState.IDLE
|
||||
self.last_frame_end: float = 0
|
||||
self.silent_interval: float = 0
|
||||
|
||||
# ----------------------------------------------------------------------- #
|
||||
# Client external interface
|
||||
# ----------------------------------------------------------------------- #
|
||||
@property
|
||||
def connected(self):
|
||||
"""Connect internal."""
|
||||
return True
|
||||
|
||||
def register(self, custom_response_class: ModbusResponse) -> None:
|
||||
"""Register a custom response class with the decoder (call **sync**).
|
||||
|
||||
:param custom_response_class: (optional) Modbus response class.
|
||||
:raises MessageRegisterException: Check exception text.
|
||||
|
||||
Use register() to add non-standard responses (like e.g. a login prompt) and
|
||||
have them interpreted automatically.
|
||||
"""
|
||||
self.framer.decoder.register(custom_response_class)
|
||||
|
||||
def close(self, reconnect=False) -> None:
|
||||
"""Close connection."""
|
||||
if reconnect:
|
||||
self.connection_lost(asyncio.TimeoutError("Server not responding"))
|
||||
else:
|
||||
self.transport_close()
|
||||
|
||||
def idle_time(self) -> float:
|
||||
"""Time before initiating next transaction (call **sync**).
|
||||
|
||||
Applications can call message functions without checking idle_time(),
|
||||
this is done automatically.
|
||||
"""
|
||||
if self.last_frame_end is None or self.silent_interval is None:
|
||||
return 0
|
||||
return self.last_frame_end + self.silent_interval
|
||||
|
||||
def execute(self, request: ModbusRequest = None) -> ModbusResponse:
|
||||
"""Execute request and get response (call **sync/async**).
|
||||
|
||||
:param request: The request to process
|
||||
:returns: The result of the request execution
|
||||
:raises ConnectionException: Check exception text.
|
||||
"""
|
||||
if self.use_sync:
|
||||
if not self.connect():
|
||||
raise ConnectionException(f"Failed to connect[{self!s}]")
|
||||
return self.transaction.execute(request)
|
||||
if not self.transport:
|
||||
raise ConnectionException(f"Not connected[{self!s}]")
|
||||
return self.async_execute(request)
|
||||
|
||||
# ----------------------------------------------------------------------- #
|
||||
# Merged client methods
|
||||
# ----------------------------------------------------------------------- #
|
||||
async def async_execute(self, request=None):
|
||||
"""Execute requests asynchronously."""
|
||||
request.transaction_id = self.transaction.getNextTID()
|
||||
packet = self.framer.buildPacket(request)
|
||||
|
||||
count = 0
|
||||
while count <= self.params.retries:
|
||||
if not count or not self.no_resend_on_retry:
|
||||
self.transport_send(packet)
|
||||
if self.params.broadcast_enable and not request.slave_id:
|
||||
resp = b"Broadcast write sent - no response expected"
|
||||
break
|
||||
try:
|
||||
req = self._build_response(request.transaction_id)
|
||||
resp = await asyncio.wait_for(
|
||||
req, timeout=self.comm_params.timeout_connect
|
||||
)
|
||||
break
|
||||
except asyncio.exceptions.TimeoutError:
|
||||
count += 1
|
||||
if count > self.params.retries:
|
||||
self.close(reconnect=True)
|
||||
raise ModbusIOException(
|
||||
f"ERROR: No response received after {self.params.retries} retries"
|
||||
)
|
||||
|
||||
return resp
|
||||
|
||||
def callback_data(self, data: bytes, addr: tuple = None) -> int:
|
||||
"""Handle received data
|
||||
|
||||
returns number of bytes consumed
|
||||
"""
|
||||
self.framer.processIncomingPacket(data, self._handle_response, slave=0)
|
||||
return len(data)
|
||||
|
||||
def callback_disconnected(self, _reason: Exception) -> None:
|
||||
"""Handle lost connection"""
|
||||
for tid in list(self.transaction):
|
||||
self.raise_future(
|
||||
self.transaction.getTransaction(tid),
|
||||
ConnectionException("Connection lost during request"),
|
||||
)
|
||||
|
||||
async def connect(self):
|
||||
"""Connect to the modbus remote host."""
|
||||
|
||||
def raise_future(self, my_future, exc):
|
||||
"""Set exception of a future if not done."""
|
||||
if not my_future.done():
|
||||
my_future.set_exception(exc)
|
||||
|
||||
def _handle_response(self, reply, **_kwargs):
|
||||
"""Handle the processed response and link to correct deferred."""
|
||||
if reply is not None:
|
||||
tid = reply.transaction_id
|
||||
if handler := self.transaction.getTransaction(tid):
|
||||
if not handler.done():
|
||||
handler.set_result(reply)
|
||||
else:
|
||||
Log.debug("Unrequested message: {}", reply, ":str")
|
||||
|
||||
def _build_response(self, tid):
|
||||
"""Return a deferred response for the current request."""
|
||||
my_future = asyncio.Future()
|
||||
if not self.transport:
|
||||
self.raise_future(my_future, ConnectionException("Client is not connected"))
|
||||
else:
|
||||
self.transaction.addTransaction(my_future, tid)
|
||||
return my_future
|
||||
|
||||
# ----------------------------------------------------------------------- #
|
||||
# Internal methods
|
||||
# ----------------------------------------------------------------------- #
|
||||
def send(self, request):
|
||||
"""Send request.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
if self.state != ModbusTransactionState.RETRYING:
|
||||
Log.debug('New Transaction state "SENDING"')
|
||||
self.state = ModbusTransactionState.SENDING
|
||||
return request
|
||||
|
||||
def recv(self, size):
|
||||
"""Receive data.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
return size
|
||||
|
||||
@classmethod
|
||||
def _get_address_family(cls, address):
|
||||
"""Get the correct address family."""
|
||||
try:
|
||||
_ = socket.inet_pton(socket.AF_INET6, address)
|
||||
except OSError: # not a valid ipv6 address
|
||||
return socket.AF_INET
|
||||
return socket.AF_INET6
|
||||
|
||||
# ----------------------------------------------------------------------- #
|
||||
# The magic methods
|
||||
# ----------------------------------------------------------------------- #
|
||||
def __enter__(self):
|
||||
"""Implement the client with enter block.
|
||||
|
||||
:returns: The current instance of the client
|
||||
:raises ConnectionException:
|
||||
"""
|
||||
|
||||
if not self.connect():
|
||||
raise ConnectionException(f"Failed to connect[{self.__str__()}]")
|
||||
return self
|
||||
|
||||
async def __aenter__(self):
|
||||
"""Implement the client with enter block.
|
||||
|
||||
:returns: The current instance of the client
|
||||
:raises ConnectionException:
|
||||
"""
|
||||
if not await self.connect():
|
||||
raise ConnectionException(f"Failed to connect[{self.__str__()}]")
|
||||
return self
|
||||
|
||||
def __exit__(self, klass, value, traceback):
|
||||
"""Implement the client with exit block."""
|
||||
self.close()
|
||||
|
||||
async def __aexit__(self, klass, value, traceback):
|
||||
"""Implement the client with exit block."""
|
||||
self.close()
|
||||
|
||||
def __str__(self):
|
||||
"""Build a string representation of the connection.
|
||||
|
||||
:returns: The string representation
|
||||
"""
|
||||
return (
|
||||
f"{self.__class__.__name__} {self.comm_params.host}:{self.comm_params.port}"
|
||||
)
|
||||
582
env/lib/python3.11/site-packages/pymodbus/client/mixin.py
vendored
Normal file
582
env/lib/python3.11/site-packages/pymodbus/client/mixin.py
vendored
Normal file
@@ -0,0 +1,582 @@
|
||||
"""Modbus Client Common."""
|
||||
import struct
|
||||
from enum import Enum
|
||||
from typing import Any, List, Tuple, Union
|
||||
|
||||
import pymodbus.bit_read_message as pdu_bit_read
|
||||
import pymodbus.bit_write_message as pdu_bit_write
|
||||
import pymodbus.diag_message as pdu_diag
|
||||
import pymodbus.file_message as pdu_file_msg
|
||||
import pymodbus.mei_message as pdu_mei
|
||||
import pymodbus.other_message as pdu_other_msg
|
||||
import pymodbus.register_read_message as pdu_reg_read
|
||||
import pymodbus.register_write_message as pdu_req_write
|
||||
from pymodbus.constants import INTERNAL_ERROR
|
||||
from pymodbus.exceptions import ModbusException
|
||||
from pymodbus.pdu import ModbusRequest, ModbusResponse
|
||||
|
||||
|
||||
class ModbusClientMixin: # pylint: disable=too-many-public-methods
|
||||
"""**ModbusClientMixin**.
|
||||
|
||||
This is an interface class to facilitate the sending requests/receiving responses like read_coils.
|
||||
execute() allows to make a call with non-standard or user defined function codes (remember to add a PDU
|
||||
in the transport class to interpret the request/response).
|
||||
|
||||
Simple modbus message call::
|
||||
|
||||
response = client.read_coils(1, 10)
|
||||
# or
|
||||
response = await client.read_coils(1, 10)
|
||||
|
||||
Advanced modbus message call::
|
||||
|
||||
request = ReadCoilsRequest(1,10)
|
||||
response = client.execute(request)
|
||||
# or
|
||||
request = ReadCoilsRequest(1,10)
|
||||
response = await client.execute(request)
|
||||
|
||||
.. tip::
|
||||
All methods can be used directly (synchronous) or
|
||||
with await <method> (asynchronous) depending on the client used.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize."""
|
||||
|
||||
def execute(self, request: ModbusRequest) -> ModbusResponse:
|
||||
"""Execute request (code ???).
|
||||
|
||||
:param request: Request to send
|
||||
:raises ModbusException:
|
||||
|
||||
Call with custom function codes.
|
||||
|
||||
.. tip::
|
||||
Response is not interpreted.
|
||||
"""
|
||||
raise ModbusException(INTERNAL_ERROR)
|
||||
|
||||
def read_coils(
|
||||
self, address: int, count: int = 1, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Read coils (code 0x01).
|
||||
|
||||
:param address: Start address to read from
|
||||
:param count: (optional) Number of coils to read
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_bit_read.ReadCoilsRequest(address, count, slave, **kwargs)
|
||||
)
|
||||
|
||||
def read_discrete_inputs(
|
||||
self, address: int, count: int = 1, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Read discrete inputs (code 0x02).
|
||||
|
||||
:param address: Start address to read from
|
||||
:param count: (optional) Number of coils to read
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_bit_read.ReadDiscreteInputsRequest(address, count, slave, **kwargs)
|
||||
)
|
||||
|
||||
def read_holding_registers(
|
||||
self, address: int, count: int = 1, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Read holding registers (code 0x03).
|
||||
|
||||
:param address: Start address to read from
|
||||
:param count: (optional) Number of coils to read
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_reg_read.ReadHoldingRegistersRequest(address, count, slave, **kwargs)
|
||||
)
|
||||
|
||||
def read_input_registers(
|
||||
self, address: int, count: int = 1, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Read input registers (code 0x04).
|
||||
|
||||
:param address: Start address to read from
|
||||
:param count: (optional) Number of coils to read
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_reg_read.ReadInputRegistersRequest(address, count, slave, **kwargs)
|
||||
)
|
||||
|
||||
def write_coil(
|
||||
self, address: int, value: bool, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Write single coil (code 0x05).
|
||||
|
||||
:param address: Address to write to
|
||||
:param value: Boolean to write
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_bit_write.WriteSingleCoilRequest(address, value, slave, **kwargs)
|
||||
)
|
||||
|
||||
def write_register(
|
||||
self, address: int, value: int, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Write register (code 0x06).
|
||||
|
||||
:param address: Address to write to
|
||||
:param value: Value to write
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_req_write.WriteSingleRegisterRequest(address, value, slave, **kwargs)
|
||||
)
|
||||
|
||||
def read_exception_status(self, slave: int = 0, **kwargs: Any) -> ModbusResponse:
|
||||
"""Read Exception Status (code 0x07).
|
||||
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(pdu_other_msg.ReadExceptionStatusRequest(slave, **kwargs))
|
||||
|
||||
def diag_query_data(
|
||||
self, msg: bytearray, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Diagnose query data (code 0x08 sub 0x00).
|
||||
|
||||
:param msg: Message to be returned
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(pdu_diag.ReturnQueryDataRequest(msg, slave=slave, **kwargs))
|
||||
|
||||
def diag_restart_communication(
|
||||
self, toggle: bool, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Diagnose restart communication (code 0x08 sub 0x01).
|
||||
|
||||
:param toggle: True if toggled.
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_diag.RestartCommunicationsOptionRequest(toggle, slave=slave, **kwargs)
|
||||
)
|
||||
|
||||
def diag_read_diagnostic_register(
|
||||
self, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Diagnose read diagnostic register (code 0x08 sub 0x02).
|
||||
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_diag.ReturnDiagnosticRegisterRequest(slave=slave, **kwargs)
|
||||
)
|
||||
|
||||
def diag_change_ascii_input_delimeter(
|
||||
self, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Diagnose change ASCII input delimiter (code 0x08 sub 0x03).
|
||||
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_diag.ChangeAsciiInputDelimiterRequest(slave=slave, **kwargs)
|
||||
)
|
||||
|
||||
def diag_force_listen_only(self, slave: int = 0, **kwargs: Any) -> ModbusResponse:
|
||||
"""Diagnose force listen only (code 0x08 sub 0x04).
|
||||
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(pdu_diag.ForceListenOnlyModeRequest(slave=slave, **kwargs))
|
||||
|
||||
def diag_clear_counters(self, slave: int = 0, **kwargs: Any) -> ModbusResponse:
|
||||
"""Diagnose clear counters (code 0x08 sub 0x0A).
|
||||
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(pdu_diag.ClearCountersRequest(slave=slave, **kwargs))
|
||||
|
||||
def diag_read_bus_message_count(
|
||||
self, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Diagnose read bus message count (code 0x08 sub 0x0B).
|
||||
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_diag.ReturnBusMessageCountRequest(slave=slave, **kwargs)
|
||||
)
|
||||
|
||||
def diag_read_bus_comm_error_count(
|
||||
self, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Diagnose read Bus Communication Error Count (code 0x08 sub 0x0C).
|
||||
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_diag.ReturnBusCommunicationErrorCountRequest(slave=slave, **kwargs)
|
||||
)
|
||||
|
||||
def diag_read_bus_exception_error_count(
|
||||
self, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Diagnose read Bus Exception Error Count (code 0x08 sub 0x0D).
|
||||
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_diag.ReturnBusExceptionErrorCountRequest(slave=slave, **kwargs)
|
||||
)
|
||||
|
||||
def diag_read_slave_message_count(
|
||||
self, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Diagnose read Slave Message Count (code 0x08 sub 0x0E).
|
||||
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_diag.ReturnSlaveMessageCountRequest(slave=slave, **kwargs)
|
||||
)
|
||||
|
||||
def diag_read_slave_no_response_count(
|
||||
self, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Diagnose read Slave No Response Count (code 0x08 sub 0x0F).
|
||||
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_diag.ReturnSlaveNoResponseCountRequest(slave=slave, **kwargs)
|
||||
)
|
||||
|
||||
def diag_read_slave_nak_count(
|
||||
self, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Diagnose read Slave NAK Count (code 0x08 sub 0x10).
|
||||
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(pdu_diag.ReturnSlaveNAKCountRequest(slave=slave, **kwargs))
|
||||
|
||||
def diag_read_slave_busy_count(
|
||||
self, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Diagnose read Slave Busy Count (code 0x08 sub 0x11).
|
||||
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(pdu_diag.ReturnSlaveBusyCountRequest(slave=slave, **kwargs))
|
||||
|
||||
def diag_read_bus_char_overrun_count(
|
||||
self, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Diagnose read Bus Character Overrun Count (code 0x08 sub 0x12).
|
||||
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_diag.ReturnSlaveBusCharacterOverrunCountRequest(slave=slave, **kwargs)
|
||||
)
|
||||
|
||||
def diag_read_iop_overrun_count(
|
||||
self, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Diagnose read Iop overrun count (code 0x08 sub 0x13).
|
||||
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_diag.ReturnIopOverrunCountRequest(slave=slave, **kwargs)
|
||||
)
|
||||
|
||||
def diag_clear_overrun_counter(
|
||||
self, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Diagnose Clear Overrun Counter and Flag (code 0x08 sub 0x14).
|
||||
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(pdu_diag.ClearOverrunCountRequest(slave=slave, **kwargs))
|
||||
|
||||
def diag_getclear_modbus_response(
|
||||
self, slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Diagnose Get/Clear modbus plus (code 0x08 sub 0x15).
|
||||
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(pdu_diag.GetClearModbusPlusRequest(slave=slave, **kwargs))
|
||||
|
||||
def diag_get_comm_event_counter(self, **kwargs: Any) -> ModbusResponse:
|
||||
"""Diagnose get event counter (code 0x0B).
|
||||
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(pdu_other_msg.GetCommEventCounterRequest(**kwargs))
|
||||
|
||||
def diag_get_comm_event_log(self, **kwargs: Any) -> ModbusResponse:
|
||||
"""Diagnose get event counter (code 0x0C).
|
||||
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(pdu_other_msg.GetCommEventLogRequest(**kwargs))
|
||||
|
||||
def write_coils(
|
||||
self,
|
||||
address: int,
|
||||
values: Union[List[bool], bool],
|
||||
slave: int = 0,
|
||||
**kwargs: Any,
|
||||
) -> ModbusResponse:
|
||||
"""Write coils (code 0x0F).
|
||||
|
||||
:param address: Start address to write to
|
||||
:param values: List of booleans to write, or a single boolean to write
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_bit_write.WriteMultipleCoilsRequest(address, values, slave, **kwargs)
|
||||
)
|
||||
|
||||
def write_registers(
|
||||
self, address: int, values: Union[List[int], int], slave: int = 0, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Write registers (code 0x10).
|
||||
|
||||
:param address: Start address to write to
|
||||
:param values: List of values to write, or a single value to write
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_req_write.WriteMultipleRegistersRequest(
|
||||
address, values, slave, **kwargs
|
||||
)
|
||||
)
|
||||
|
||||
def report_slave_id(self, slave: int = 0, **kwargs: Any) -> ModbusResponse:
|
||||
"""Report slave ID (code 0x11).
|
||||
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(pdu_other_msg.ReportSlaveIdRequest(slave, **kwargs))
|
||||
|
||||
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)
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(pdu_file_msg.ReadFileRecordRequest(records, **kwargs))
|
||||
|
||||
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)
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(pdu_file_msg.WriteFileRecordRequest(records, **kwargs))
|
||||
|
||||
def mask_write_register(
|
||||
self,
|
||||
address: int = 0x0000,
|
||||
and_mask: int = 0xFFFF,
|
||||
or_mask: int = 0x0000,
|
||||
**kwargs: Any,
|
||||
) -> ModbusResponse:
|
||||
"""Mask write register (code 0x16).
|
||||
|
||||
:param address: The mask pointer address (0x0000 to 0xffff)
|
||||
:param and_mask: The and bitmask to apply to the register address
|
||||
:param or_mask: The or bitmask to apply to the register address
|
||||
:param kwargs: (optional) Experimental parameters.
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_req_write.MaskWriteRegisterRequest(address, and_mask, or_mask, **kwargs)
|
||||
)
|
||||
|
||||
def readwrite_registers(
|
||||
self,
|
||||
read_address: int = 0,
|
||||
read_count: int = 0,
|
||||
write_address: int = 0,
|
||||
values: Union[List[int], int] = 0,
|
||||
slave: int = 0,
|
||||
**kwargs,
|
||||
) -> ModbusResponse:
|
||||
"""Read/Write registers (code 0x17).
|
||||
|
||||
:param read_address: The address to start reading from
|
||||
:param read_count: The number of registers to read from address
|
||||
:param write_address: The address to start writing to
|
||||
:param values: List of values to write, or a single value to write
|
||||
:param slave: (optional) Modbus slave ID
|
||||
:param kwargs:
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_reg_read.ReadWriteMultipleRegistersRequest(
|
||||
read_address=read_address,
|
||||
read_count=read_count,
|
||||
write_address=write_address,
|
||||
write_registers=values,
|
||||
slave=slave,
|
||||
**kwargs,
|
||||
)
|
||||
)
|
||||
|
||||
def read_fifo_queue(self, address: int = 0x0000, **kwargs: Any) -> ModbusResponse:
|
||||
"""Read FIFO queue (code 0x18).
|
||||
|
||||
:param address: The address to start reading from
|
||||
:param kwargs:
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(pdu_file_msg.ReadFifoQueueRequest(address, **kwargs))
|
||||
|
||||
# code 0x2B sub 0x0D: CANopen General Reference Request and Response, NOT IMPLEMENTED
|
||||
|
||||
def read_device_information(
|
||||
self, read_code: int = None, object_id: int = 0x00, **kwargs: Any
|
||||
) -> ModbusResponse:
|
||||
"""Read FIFO queue (code 0x2B sub 0x0E).
|
||||
|
||||
:param read_code: The device information read code
|
||||
:param object_id: The object to read from
|
||||
:param kwargs:
|
||||
:raises ModbusException:
|
||||
"""
|
||||
return self.execute(
|
||||
pdu_mei.ReadDeviceInformationRequest(read_code, object_id, **kwargs)
|
||||
)
|
||||
|
||||
# ------------------
|
||||
# Converter methods
|
||||
# ------------------
|
||||
|
||||
class DATATYPE(Enum):
|
||||
"""Datatype enum (name and number of bytes), used for convert_* calls."""
|
||||
|
||||
INT16 = ("h", 1)
|
||||
UINT16 = ("H", 1)
|
||||
INT32 = ("i", 2)
|
||||
UINT32 = ("I", 2)
|
||||
INT64 = ("q", 4)
|
||||
UINT64 = ("Q", 4)
|
||||
FLOAT32 = ("f", 2)
|
||||
FLOAT64 = ("d", 4)
|
||||
STRING = ("s", 0)
|
||||
|
||||
@classmethod
|
||||
def convert_from_registers(
|
||||
cls, registers: List[int], data_type: DATATYPE
|
||||
) -> Union[int, float, str]:
|
||||
"""Convert registers to int/float/str.
|
||||
|
||||
:param registers: list of registers received from e.g. read_holding_registers()
|
||||
:param data_type: data type to convert to
|
||||
:returns: int, float or str depending on "data_type"
|
||||
:raises ModbusException: when size of registers is not 1, 2 or 4
|
||||
"""
|
||||
byte_list = bytearray()
|
||||
for x in registers:
|
||||
byte_list.extend(int.to_bytes(x, 2, "big"))
|
||||
if data_type == cls.DATATYPE.STRING:
|
||||
if byte_list[-1:] == b"\00":
|
||||
byte_list = byte_list[:-1]
|
||||
return byte_list.decode("utf-8")
|
||||
if len(registers) != data_type.value[1]:
|
||||
raise ModbusException(
|
||||
f"Illegal size ({len(registers)}) of register array, cannot convert!"
|
||||
)
|
||||
return struct.unpack(f">{data_type.value[0]}", byte_list)[0]
|
||||
|
||||
@classmethod
|
||||
def convert_to_registers(
|
||||
cls, value: Union[int, float, str], data_type: DATATYPE
|
||||
) -> List[int]:
|
||||
"""Convert int/float/str to registers (16/32/64 bit).
|
||||
|
||||
:param value: value to be converted
|
||||
:param data_type: data type to be encoded as registers
|
||||
:returns: List of registers, can be used directly in e.g. write_registers()
|
||||
:raises TypeError: when there is a mismatch between data_type and value
|
||||
"""
|
||||
if data_type == cls.DATATYPE.STRING:
|
||||
if not isinstance(value, str):
|
||||
raise TypeError(f"Value should be string but is {type(value)}.")
|
||||
byte_list = value.encode()
|
||||
if len(byte_list) % 2:
|
||||
byte_list += b"\x00"
|
||||
else:
|
||||
byte_list = struct.pack(f">{data_type.value[0]}", value)
|
||||
regs = [
|
||||
int.from_bytes(byte_list[x : x + 2], "big")
|
||||
for x in range(0, len(byte_list), 2)
|
||||
]
|
||||
return regs
|
||||
291
env/lib/python3.11/site-packages/pymodbus/client/serial.py
vendored
Normal file
291
env/lib/python3.11/site-packages/pymodbus/client/serial.py
vendored
Normal file
@@ -0,0 +1,291 @@
|
||||
"""Modbus client async serial communication."""
|
||||
import asyncio
|
||||
import time
|
||||
from contextlib import suppress
|
||||
from functools import partial
|
||||
from typing import Any, Type
|
||||
|
||||
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.logging import Log
|
||||
from pymodbus.transport import CommType
|
||||
from pymodbus.utilities import ModbusTransactionState
|
||||
|
||||
|
||||
with suppress(ImportError):
|
||||
import serial
|
||||
|
||||
|
||||
class AsyncModbusSerialClient(ModbusBaseClient, asyncio.Protocol):
|
||||
"""**AsyncModbusSerialClient**.
|
||||
|
||||
:param port: Serial port used for communication.
|
||||
:param framer: (optional) Framer class.
|
||||
:param baudrate: (optional) Bits per second.
|
||||
:param bytesize: (optional) Number of bits per byte 7-8.
|
||||
:param parity: (optional) 'E'ven, 'O'dd or 'N'one
|
||||
:param stopbits: (optional) Number of stop bits 0-2¡.
|
||||
:param handle_local_echo: (optional) Discard local echo from dongle.
|
||||
:param kwargs: (optional) Experimental parameters
|
||||
|
||||
The serial communication is RS-485 based, and usually used with a usb RS485 dongle.
|
||||
|
||||
Example::
|
||||
|
||||
from pymodbus.client import AsyncModbusSerialClient
|
||||
|
||||
async def run():
|
||||
client = AsyncModbusSerialClient("dev/serial0")
|
||||
|
||||
await client.connect()
|
||||
...
|
||||
client.close()
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
port: str,
|
||||
framer: Type[ModbusFramer] = ModbusRtuFramer,
|
||||
baudrate: int = 19200,
|
||||
bytesize: int = 8,
|
||||
parity: str = "N",
|
||||
stopbits: int = 1,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize Asyncio Modbus Serial Client."""
|
||||
asyncio.Protocol.__init__(self)
|
||||
ModbusBaseClient.__init__(
|
||||
self,
|
||||
framer=framer,
|
||||
CommType=CommType.SERIAL,
|
||||
host=port,
|
||||
baudrate=baudrate,
|
||||
bytesize=bytesize,
|
||||
parity=parity,
|
||||
stopbits=stopbits,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@property
|
||||
def connected(self):
|
||||
"""Connect internal."""
|
||||
return self.is_active()
|
||||
|
||||
async def connect(self) -> bool:
|
||||
"""Connect Async client."""
|
||||
self.reset_delay()
|
||||
Log.debug("Connecting to {}.", self.comm_params.host)
|
||||
return await self.transport_connect()
|
||||
|
||||
|
||||
class ModbusSerialClient(ModbusBaseClient):
|
||||
"""**ModbusSerialClient**.
|
||||
|
||||
:param port: Serial port used for communication.
|
||||
:param framer: (optional) Framer class.
|
||||
:param baudrate: (optional) Bits per second.
|
||||
:param bytesize: (optional) Number of bits per byte 7-8.
|
||||
:param parity: (optional) 'E'ven, 'O'dd or 'N'one
|
||||
:param stopbits: (optional) Number of stop bits 0-2¡.
|
||||
:param handle_local_echo: (optional) Discard local echo from dongle.
|
||||
:param kwargs: (optional) Experimental parameters
|
||||
|
||||
The serial communication is RS-485 based, and usually used with a usb RS485 dongle.
|
||||
|
||||
Example::
|
||||
|
||||
from pymodbus.client import ModbusSerialClient
|
||||
|
||||
def run():
|
||||
client = ModbusSerialClient("dev/serial0")
|
||||
|
||||
client.connect()
|
||||
...
|
||||
client.close()
|
||||
|
||||
|
||||
Remark: There are no automatic reconnect as with AsyncModbusSerialClient
|
||||
"""
|
||||
|
||||
state = ModbusTransactionState.IDLE
|
||||
inter_char_timeout: float = 0
|
||||
silent_interval: float = 0
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
port: str,
|
||||
framer: Type[ModbusFramer] = ModbusRtuFramer,
|
||||
baudrate: int = 19200,
|
||||
bytesize: int = 8,
|
||||
parity: str = "N",
|
||||
stopbits: int = 1,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize Modbus Serial Client."""
|
||||
self.transport = None
|
||||
kwargs["use_sync"] = True
|
||||
ModbusBaseClient.__init__(
|
||||
self,
|
||||
framer=framer,
|
||||
CommType=CommType.SERIAL,
|
||||
host=port,
|
||||
baudrate=baudrate,
|
||||
bytesize=bytesize,
|
||||
parity=parity,
|
||||
stopbits=stopbits,
|
||||
**kwargs,
|
||||
)
|
||||
self.socket = None
|
||||
|
||||
self.last_frame_end = None
|
||||
|
||||
self._t0 = float(1 + 8 + 2) / self.comm_params.baudrate
|
||||
|
||||
"""
|
||||
The minimum delay is 0.01s and the maximum can be set to 0.05s.
|
||||
Setting too large a setting affects efficiency.
|
||||
"""
|
||||
self._recv_interval = (
|
||||
(round((100 * self._t0), 2) + 0.01)
|
||||
if (round((100 * self._t0), 2) + 0.01) < 0.05
|
||||
else 0.05
|
||||
)
|
||||
|
||||
if self.comm_params.baudrate > 19200:
|
||||
self.silent_interval = 1.75 / 1000 # ms
|
||||
else:
|
||||
self.inter_char_timeout = 1.5 * self._t0
|
||||
self.silent_interval = 3.5 * self._t0
|
||||
self.silent_interval = round(self.silent_interval, 6)
|
||||
|
||||
@property
|
||||
def connected(self):
|
||||
"""Connect internal."""
|
||||
return self.connect()
|
||||
|
||||
def connect(self): # pylint: disable=invalid-overridden-method
|
||||
"""Connect to the modbus serial server."""
|
||||
if self.socket:
|
||||
return True
|
||||
try:
|
||||
self.socket = serial.serial_for_url(
|
||||
self.comm_params.host,
|
||||
timeout=self.comm_params.timeout_connect,
|
||||
bytesize=self.comm_params.bytesize,
|
||||
stopbits=self.comm_params.stopbits,
|
||||
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
|
||||
except serial.SerialException as msg:
|
||||
Log.error("{}", msg)
|
||||
self.close()
|
||||
return self.socket is not None
|
||||
|
||||
def close(self): # pylint: disable=arguments-differ
|
||||
"""Close the underlying socket connection."""
|
||||
if self.socket:
|
||||
self.socket.close()
|
||||
self.socket = None
|
||||
|
||||
def _in_waiting(self):
|
||||
"""Return _in_waiting."""
|
||||
in_waiting = "in_waiting" if hasattr(self.socket, "in_waiting") else "inWaiting"
|
||||
|
||||
if in_waiting == "in_waiting":
|
||||
waitingbytes = getattr(self.socket, in_waiting)
|
||||
else:
|
||||
waitingbytes = getattr(self.socket, in_waiting)()
|
||||
return waitingbytes
|
||||
|
||||
def send(self, request):
|
||||
"""Send data on the underlying socket.
|
||||
|
||||
If receive buffer still holds some data then flush it.
|
||||
|
||||
Sleep if last send finished less than 3.5 character times ago.
|
||||
"""
|
||||
super().send(request)
|
||||
if not self.socket:
|
||||
raise ConnectionException(str(self))
|
||||
if request:
|
||||
try:
|
||||
if waitingbytes := self._in_waiting():
|
||||
result = self.socket.read(waitingbytes)
|
||||
if self.state == ModbusTransactionState.RETRYING:
|
||||
Log.debug(
|
||||
"Sending available data in recv buffer {}", result, ":hex"
|
||||
)
|
||||
return result
|
||||
Log.warning("Cleanup recv buffer before send: {}", result, ":hex")
|
||||
except NotImplementedError:
|
||||
pass
|
||||
if self.state != ModbusTransactionState.SENDING:
|
||||
Log.debug('New Transaction state "SENDING"')
|
||||
self.state = ModbusTransactionState.SENDING
|
||||
size = self.socket.write(request)
|
||||
return size
|
||||
return 0
|
||||
|
||||
def _wait_for_data(self):
|
||||
"""Wait for data."""
|
||||
size = 0
|
||||
more_data = False
|
||||
if (
|
||||
self.comm_params.timeout_connect is not None
|
||||
and self.comm_params.timeout_connect
|
||||
):
|
||||
condition = partial(
|
||||
lambda start, timeout: (time.time() - start) <= timeout,
|
||||
timeout=self.comm_params.timeout_connect,
|
||||
)
|
||||
else:
|
||||
condition = partial(lambda dummy1, dummy2: True, dummy2=None)
|
||||
start = time.time()
|
||||
while condition(start):
|
||||
available = self._in_waiting()
|
||||
if (more_data and not available) or (more_data and available == size):
|
||||
break
|
||||
if available and available != size:
|
||||
more_data = True
|
||||
size = available
|
||||
time.sleep(self._recv_interval)
|
||||
return size
|
||||
|
||||
def recv(self, size):
|
||||
"""Read data from the underlying descriptor."""
|
||||
super().recv(size)
|
||||
if not self.socket:
|
||||
raise ConnectionException(
|
||||
self.__str__() # pylint: disable=unnecessary-dunder-call
|
||||
)
|
||||
if size is None:
|
||||
size = self._wait_for_data()
|
||||
if size > self._in_waiting():
|
||||
self._wait_for_data()
|
||||
result = self.socket.read(size)
|
||||
return result
|
||||
|
||||
def is_socket_open(self):
|
||||
"""Check if socket is open."""
|
||||
if self.socket:
|
||||
if hasattr(self.socket, "is_open"):
|
||||
return self.socket.is_open
|
||||
return self.socket.isOpen()
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
"""Build a string representation of the connection."""
|
||||
return f"ModbusSerialClient({self.framer} baud[{self.comm_params.baudrate}])"
|
||||
|
||||
def __repr__(self):
|
||||
"""Return string representation."""
|
||||
return (
|
||||
f"<{self.__class__.__name__} at {hex(id(self))} socket={self.socket}, "
|
||||
f"framer={self.framer}, timeout={self.comm_params.timeout_connect}>"
|
||||
)
|
||||
275
env/lib/python3.11/site-packages/pymodbus/client/tcp.py
vendored
Normal file
275
env/lib/python3.11/site-packages/pymodbus/client/tcp.py
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
"""Modbus client async TCP communication."""
|
||||
import asyncio
|
||||
import select
|
||||
import socket
|
||||
import time
|
||||
from typing import Any, Tuple, Type
|
||||
|
||||
from pymodbus.client.base import ModbusBaseClient
|
||||
from pymodbus.exceptions import ConnectionException
|
||||
from pymodbus.framer import ModbusFramer
|
||||
from pymodbus.framer.socket_framer import ModbusSocketFramer
|
||||
from pymodbus.logging import Log
|
||||
from pymodbus.transport import CommType
|
||||
from pymodbus.utilities import ModbusTransactionState
|
||||
|
||||
|
||||
class AsyncModbusTcpClient(ModbusBaseClient, asyncio.Protocol):
|
||||
"""**AsyncModbusTcpClient**.
|
||||
|
||||
:param host: Host IP address or host name
|
||||
:param port: (optional) Port used for communication
|
||||
:param framer: (optional) Framer class
|
||||
:param source_address: (optional) source address of client
|
||||
:param kwargs: (optional) Experimental parameters
|
||||
|
||||
Example::
|
||||
|
||||
from pymodbus.client import AsyncModbusTcpClient
|
||||
|
||||
async def run():
|
||||
client = AsyncModbusTcpClient("localhost")
|
||||
|
||||
await client.connect()
|
||||
...
|
||||
client.close()
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str,
|
||||
port: int = 502,
|
||||
framer: Type[ModbusFramer] = ModbusSocketFramer,
|
||||
source_address: Tuple[str, int] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize Asyncio Modbus TCP Client."""
|
||||
asyncio.Protocol.__init__(self)
|
||||
if "CommType" not in kwargs:
|
||||
kwargs["CommType"] = CommType.TCP
|
||||
if source_address:
|
||||
kwargs["source_address"] = source_address
|
||||
ModbusBaseClient.__init__(
|
||||
self,
|
||||
framer=framer,
|
||||
host=host,
|
||||
port=port,
|
||||
**kwargs,
|
||||
)
|
||||
self.params.source_address = source_address
|
||||
|
||||
async def connect(self) -> bool:
|
||||
"""Initiate connection to start client."""
|
||||
self.reset_delay()
|
||||
Log.debug(
|
||||
"Connecting to {}:{}.",
|
||||
self.comm_params.host,
|
||||
self.comm_params.port,
|
||||
)
|
||||
return await self.transport_connect()
|
||||
|
||||
@property
|
||||
def connected(self):
|
||||
"""Return true if connected."""
|
||||
return self.is_active()
|
||||
|
||||
|
||||
class ModbusTcpClient(ModbusBaseClient):
|
||||
"""**ModbusTcpClient**.
|
||||
|
||||
:param host: Host IP address or host name
|
||||
:param port: (optional) Port used for communication
|
||||
:param framer: (optional) Framer class
|
||||
:param source_address: (optional) source address of client
|
||||
:param kwargs: (optional) Experimental parameters
|
||||
|
||||
Example::
|
||||
|
||||
from pymodbus.client import ModbusTcpClient
|
||||
|
||||
async def run():
|
||||
client = ModbusTcpClient("localhost")
|
||||
|
||||
client.connect()
|
||||
...
|
||||
client.close()
|
||||
|
||||
Remark: There are no automatic reconnect as with AsyncModbusTcpClient
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str,
|
||||
port: int = 502,
|
||||
framer: Type[ModbusFramer] = ModbusSocketFramer,
|
||||
source_address: Tuple[str, int] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize Modbus TCP Client."""
|
||||
if "CommType" not in kwargs:
|
||||
kwargs["CommType"] = CommType.TCP
|
||||
kwargs["use_sync"] = True
|
||||
self.transport = None
|
||||
super().__init__(framer=framer, host=host, port=port, **kwargs)
|
||||
self.params.source_address = source_address
|
||||
self.socket = None
|
||||
|
||||
@property
|
||||
def connected(self):
|
||||
"""Connect internal."""
|
||||
return self.socket is not None
|
||||
|
||||
def connect(self): # pylint: disable=invalid-overridden-method
|
||||
"""Connect to the modbus tcp server."""
|
||||
if self.socket:
|
||||
return True
|
||||
try:
|
||||
self.socket = socket.create_connection(
|
||||
(self.comm_params.host, self.comm_params.port),
|
||||
timeout=self.comm_params.timeout_connect,
|
||||
source_address=self.params.source_address,
|
||||
)
|
||||
Log.debug(
|
||||
"Connection to Modbus server established. Socket {}",
|
||||
self.socket.getsockname(),
|
||||
)
|
||||
except OSError as msg:
|
||||
Log.error(
|
||||
"Connection to ({}, {}) failed: {}",
|
||||
self.comm_params.host,
|
||||
self.comm_params.port,
|
||||
msg,
|
||||
)
|
||||
self.close()
|
||||
return self.socket is not None
|
||||
|
||||
def close(self): # pylint: disable=arguments-differ
|
||||
"""Close the underlying socket connection."""
|
||||
if self.socket:
|
||||
self.socket.close()
|
||||
self.socket = None
|
||||
|
||||
def _check_read_buffer(self):
|
||||
"""Check read buffer."""
|
||||
time_ = time.time()
|
||||
end = time_ + self.comm_params.timeout_connect
|
||||
data = None
|
||||
ready = select.select([self.socket], [], [], end - time_)
|
||||
if ready[0]:
|
||||
data = self.socket.recv(1024)
|
||||
return data
|
||||
|
||||
def send(self, request):
|
||||
"""Send data on the underlying socket."""
|
||||
super().send(request)
|
||||
if not self.socket:
|
||||
raise ConnectionException(str(self))
|
||||
if self.state == ModbusTransactionState.RETRYING:
|
||||
if data := self._check_read_buffer():
|
||||
return data
|
||||
|
||||
if request:
|
||||
return self.socket.send(request)
|
||||
return 0
|
||||
|
||||
def recv(self, size):
|
||||
"""Read data from the underlying descriptor."""
|
||||
super().recv(size)
|
||||
if not self.socket:
|
||||
raise ConnectionException(str(self))
|
||||
|
||||
# socket.recv(size) waits until it gets some data from the host but
|
||||
# not necessarily the entire response that can be fragmented in
|
||||
# many packets.
|
||||
# To avoid split responses to be recognized as invalid
|
||||
# messages and to be discarded, loops socket.recv until full data
|
||||
# is received or timeout is expired.
|
||||
# If timeout expires returns the read data, also if its length is
|
||||
# less than the expected size.
|
||||
self.socket.setblocking(0)
|
||||
|
||||
timeout = self.comm_params.timeout_connect
|
||||
|
||||
# If size isn't specified read up to 4096 bytes at a time.
|
||||
if size is None:
|
||||
recv_size = 4096
|
||||
else:
|
||||
recv_size = size
|
||||
|
||||
data = []
|
||||
data_length = 0
|
||||
time_ = time.time()
|
||||
end = time_ + timeout
|
||||
while recv_size > 0:
|
||||
try:
|
||||
ready = select.select([self.socket], [], [], end - time_)
|
||||
except ValueError:
|
||||
return self._handle_abrupt_socket_close(size, data, time.time() - time_)
|
||||
if ready[0]:
|
||||
if (recv_data := self.socket.recv(recv_size)) == b"":
|
||||
return self._handle_abrupt_socket_close(
|
||||
size, data, time.time() - time_
|
||||
)
|
||||
data.append(recv_data)
|
||||
data_length += len(recv_data)
|
||||
time_ = time.time()
|
||||
|
||||
# If size isn't specified continue to read until timeout expires.
|
||||
if size:
|
||||
recv_size = size - data_length
|
||||
|
||||
# Timeout is reduced also if some data has been received in order
|
||||
# to avoid infinite loops when there isn't an expected response
|
||||
# size and the slave sends noisy data continuously.
|
||||
if time_ > end:
|
||||
break
|
||||
|
||||
return b"".join(data)
|
||||
|
||||
def _handle_abrupt_socket_close(self, size, data, duration):
|
||||
"""Handle unexpected socket close by remote end.
|
||||
|
||||
Intended to be invoked after determining that the remote end
|
||||
has unexpectedly closed the connection, to clean up and handle
|
||||
the situation appropriately.
|
||||
|
||||
:param size: The number of bytes that was attempted to read
|
||||
:param data: The actual data returned
|
||||
:param duration: Duration from the read was first attempted
|
||||
until it was determined that the remote closed the
|
||||
socket
|
||||
:return: The more than zero bytes read from the remote end
|
||||
:raises ConnectionException: If the remote end didn't send any
|
||||
data at all before closing the connection.
|
||||
"""
|
||||
self.close()
|
||||
size_txt = size if size else "unbounded read"
|
||||
readsize = f"read of {size_txt} bytes"
|
||||
msg = (
|
||||
f"{self}: Connection unexpectedly closed "
|
||||
f"{duration} seconds into {readsize}"
|
||||
)
|
||||
if data:
|
||||
result = b"".join(data)
|
||||
Log.warning(" after returning {} bytes: {} ", len(result), result)
|
||||
return result
|
||||
msg += " without response from slave before it closed connection"
|
||||
raise ConnectionException(msg)
|
||||
|
||||
def is_socket_open(self):
|
||||
"""Check if socket is open."""
|
||||
return self.socket is not None
|
||||
|
||||
def __str__(self):
|
||||
"""Build a string representation of the connection.
|
||||
|
||||
:returns: The string representation
|
||||
"""
|
||||
return f"ModbusTcpClient({self.comm_params.host}:{self.comm_params.port})"
|
||||
|
||||
def __repr__(self):
|
||||
"""Return string representation."""
|
||||
return (
|
||||
f"<{self.__class__.__name__} at {hex(id(self))} socket={self.socket}, "
|
||||
f"ipaddr={self.comm_params.host}, port={self.comm_params.port}, timeout={self.comm_params.timeout_connect}>"
|
||||
)
|
||||
171
env/lib/python3.11/site-packages/pymodbus/client/tls.py
vendored
Normal file
171
env/lib/python3.11/site-packages/pymodbus/client/tls.py
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
"""Modbus client async TLS communication."""
|
||||
import socket
|
||||
import ssl
|
||||
from typing import Any, Type
|
||||
|
||||
from pymodbus.client.tcp import AsyncModbusTcpClient, ModbusTcpClient
|
||||
from pymodbus.framer import ModbusFramer
|
||||
from pymodbus.framer.tls_framer import ModbusTlsFramer
|
||||
from pymodbus.logging import Log
|
||||
from pymodbus.transport import CommParams, CommType
|
||||
|
||||
|
||||
class AsyncModbusTlsClient(AsyncModbusTcpClient):
|
||||
"""**AsyncModbusTlsClient**.
|
||||
|
||||
:param host: Host IP address or host name
|
||||
:param port: (optional) Port used for communication
|
||||
:param framer: (optional) Framer class
|
||||
:param source_address: (optional) Source address of client
|
||||
:param sslctx: (optional) SSLContext to use for TLS
|
||||
:param certfile: (optional) Cert file path for TLS server request
|
||||
:param keyfile: (optional) Key file path for TLS server request
|
||||
:param password: (optional) Password for for decrypting private key file
|
||||
:param server_hostname: (optional) Bind certificate to host
|
||||
:param kwargs: (optional) Experimental parameters
|
||||
|
||||
..tip::
|
||||
See ModbusBaseClient for common parameters.
|
||||
|
||||
Example::
|
||||
|
||||
from pymodbus.client import AsyncModbusTlsClient
|
||||
|
||||
async def run():
|
||||
client = AsyncModbusTlsClient("localhost")
|
||||
|
||||
await client.connect()
|
||||
...
|
||||
client.close()
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str,
|
||||
port: int = 802,
|
||||
framer: Type[ModbusFramer] = ModbusTlsFramer,
|
||||
sslctx: ssl.SSLContext = None,
|
||||
certfile: str = None,
|
||||
keyfile: str = None,
|
||||
password: str = None,
|
||||
server_hostname: str = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
"""Initialize Asyncio Modbus TLS Client."""
|
||||
AsyncModbusTcpClient.__init__(
|
||||
self,
|
||||
host,
|
||||
port=port,
|
||||
framer=framer,
|
||||
CommType=CommType.TLS,
|
||||
sslctx=CommParams.generate_ssl(
|
||||
False, certfile, keyfile, password, sslctx=sslctx
|
||||
),
|
||||
**kwargs,
|
||||
)
|
||||
self.params.server_hostname = server_hostname
|
||||
|
||||
async def connect(self) -> bool:
|
||||
"""Initiate connection to start client."""
|
||||
self.reset_delay()
|
||||
Log.debug(
|
||||
"Connecting to {}:{}.",
|
||||
self.comm_params.host,
|
||||
self.comm_params.port,
|
||||
)
|
||||
return await self.transport_connect()
|
||||
|
||||
|
||||
class ModbusTlsClient(ModbusTcpClient):
|
||||
"""**ModbusTlsClient**.
|
||||
|
||||
:param host: Host IP address or host name
|
||||
:param port: (optional) Port used for communication
|
||||
:param framer: (optional) Framer class
|
||||
:param source_address: (optional) Source address of client
|
||||
:param sslctx: (optional) SSLContext to use for TLS
|
||||
:param certfile: (optional) Cert file path for TLS server request
|
||||
:param keyfile: (optional) Key file path for TLS server request
|
||||
:param password: (optional) Password for decrypting private key file
|
||||
:param server_hostname: (optional) Bind certificate to host
|
||||
:param kwargs: (optional) Experimental parameters
|
||||
|
||||
..tip::
|
||||
See ModbusBaseClient for common parameters.
|
||||
|
||||
Example::
|
||||
|
||||
from pymodbus.client import ModbusTlsClient
|
||||
|
||||
async def run():
|
||||
client = ModbusTlsClient("localhost")
|
||||
|
||||
client.connect()
|
||||
...
|
||||
client.close()
|
||||
|
||||
|
||||
Remark: There are no automatic reconnect as with AsyncModbusTlsClient
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str,
|
||||
port: int = 802,
|
||||
framer: Type[ModbusFramer] = ModbusTlsFramer,
|
||||
sslctx: ssl.SSLContext = None,
|
||||
certfile: str = None,
|
||||
keyfile: str = None,
|
||||
password: str = None,
|
||||
server_hostname: str = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
"""Initialize Modbus TLS Client."""
|
||||
self.transport = None
|
||||
super().__init__(
|
||||
host, CommType=CommType.TLS, port=port, framer=framer, **kwargs
|
||||
)
|
||||
self.sslctx = CommParams.generate_ssl(
|
||||
False, certfile, keyfile, password, sslctx=sslctx
|
||||
)
|
||||
self.params.server_hostname = server_hostname
|
||||
|
||||
@property
|
||||
def connected(self):
|
||||
"""Connect internal."""
|
||||
return self.transport is not None
|
||||
|
||||
def connect(self):
|
||||
"""Connect to the modbus tls server."""
|
||||
if self.socket:
|
||||
return True
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
if self.params.source_address:
|
||||
sock.bind(self.params.source_address)
|
||||
self.socket = self.sslctx.wrap_socket(
|
||||
sock, server_side=False, server_hostname=self.comm_params.host
|
||||
)
|
||||
self.socket.settimeout(self.comm_params.timeout_connect)
|
||||
self.socket.connect((self.comm_params.host, self.comm_params.port))
|
||||
except OSError as msg:
|
||||
Log.error(
|
||||
"Connection to ({}, {}) failed: {}",
|
||||
self.comm_params.host,
|
||||
self.comm_params.port,
|
||||
msg,
|
||||
)
|
||||
self.close()
|
||||
return self.socket is not None
|
||||
|
||||
def __str__(self):
|
||||
"""Build a string representation of the connection."""
|
||||
return f"ModbusTlsClient({self.comm_params.host}:{self.comm_params.port})"
|
||||
|
||||
def __repr__(self):
|
||||
"""Return string representation."""
|
||||
return (
|
||||
f"<{self.__class__.__name__} at {hex(id(self))} socket={self.socket}, "
|
||||
f"ipaddr={self.comm_params.host}, port={self.comm_params.port}, sslctx={self.sslctx}, "
|
||||
f"timeout={self.comm_params.timeout_connect}>"
|
||||
)
|
||||
190
env/lib/python3.11/site-packages/pymodbus/client/udp.py
vendored
Normal file
190
env/lib/python3.11/site-packages/pymodbus/client/udp.py
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
"""Modbus client async UDP communication."""
|
||||
import asyncio
|
||||
import socket
|
||||
from typing import Any, Tuple, Type
|
||||
|
||||
from pymodbus.client.base import ModbusBaseClient
|
||||
from pymodbus.exceptions import ConnectionException
|
||||
from pymodbus.framer import ModbusFramer
|
||||
from pymodbus.framer.socket_framer import ModbusSocketFramer
|
||||
from pymodbus.logging import Log
|
||||
from pymodbus.transport import CommType
|
||||
|
||||
|
||||
DGRAM_TYPE = socket.SOCK_DGRAM
|
||||
|
||||
|
||||
class AsyncModbusUdpClient(
|
||||
ModbusBaseClient, asyncio.Protocol, asyncio.DatagramProtocol
|
||||
):
|
||||
"""**AsyncModbusUdpClient**.
|
||||
|
||||
:param host: Host IP address or host name
|
||||
:param port: (optional) Port used for communication.
|
||||
:param framer: (optional) Framer class.
|
||||
:param source_address: (optional) source address of client,
|
||||
:param kwargs: (optional) Experimental parameters
|
||||
|
||||
..tip::
|
||||
See ModbusBaseClient for common parameters.
|
||||
|
||||
Example::
|
||||
|
||||
from pymodbus.client import AsyncModbusUdpClient
|
||||
|
||||
async def run():
|
||||
client = AsyncModbusUdpClient("localhost")
|
||||
|
||||
await client.connect()
|
||||
...
|
||||
client.close()
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str,
|
||||
port: int = 502,
|
||||
framer: Type[ModbusFramer] = ModbusSocketFramer,
|
||||
source_address: Tuple[str, int] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize Asyncio Modbus UDP Client."""
|
||||
asyncio.DatagramProtocol.__init__(self)
|
||||
asyncio.Protocol.__init__(self)
|
||||
ModbusBaseClient.__init__(
|
||||
self, framer=framer, CommType=CommType.UDP, host=host, port=port, **kwargs
|
||||
)
|
||||
self.params.source_address = source_address
|
||||
|
||||
@property
|
||||
def connected(self):
|
||||
"""Return true if connected."""
|
||||
return self.is_active()
|
||||
|
||||
async def connect(self) -> bool:
|
||||
"""Start reconnecting asynchronous udp client.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
self.reset_delay()
|
||||
Log.debug(
|
||||
"Connecting to {}:{}.",
|
||||
self.comm_params.host,
|
||||
self.comm_params.port,
|
||||
)
|
||||
return await self.transport_connect()
|
||||
|
||||
|
||||
class ModbusUdpClient(ModbusBaseClient):
|
||||
"""**ModbusUdpClient**.
|
||||
|
||||
:param host: Host IP address or host name
|
||||
:param port: (optional) Port used for communication.
|
||||
:param framer: (optional) Framer class.
|
||||
:param source_address: (optional) source address of client,
|
||||
:param kwargs: (optional) Experimental parameters
|
||||
|
||||
..tip::
|
||||
See ModbusBaseClient for common parameters.
|
||||
|
||||
Example::
|
||||
|
||||
from pymodbus.client import ModbusUdpClient
|
||||
|
||||
async def run():
|
||||
client = ModbusUdpClient("localhost")
|
||||
|
||||
client.connect()
|
||||
...
|
||||
client.close()
|
||||
|
||||
Remark: There are no automatic reconnect as with AsyncModbusUdpClient
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str,
|
||||
port: int = 502,
|
||||
framer: Type[ModbusFramer] = ModbusSocketFramer,
|
||||
source_address: Tuple[str, int] = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize Modbus UDP Client."""
|
||||
kwargs["use_sync"] = True
|
||||
self.transport = None
|
||||
super().__init__(
|
||||
framer=framer, port=port, host=host, CommType=CommType.UDP, **kwargs
|
||||
)
|
||||
self.params.source_address = source_address
|
||||
|
||||
self.socket = None
|
||||
|
||||
@property
|
||||
def connected(self):
|
||||
"""Connect internal."""
|
||||
return self.socket is not None
|
||||
|
||||
def connect(self): # pylint: disable=invalid-overridden-method
|
||||
"""Connect to the modbus tcp server.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
if self.socket:
|
||||
return True
|
||||
try:
|
||||
family = ModbusUdpClient._get_address_family(self.comm_params.host)
|
||||
self.socket = socket.socket(family, socket.SOCK_DGRAM)
|
||||
self.socket.settimeout(self.comm_params.timeout_connect)
|
||||
except OSError as exc:
|
||||
Log.error("Unable to create udp socket {}", exc)
|
||||
self.close()
|
||||
return self.socket is not None
|
||||
|
||||
def close(self): # pylint: disable=arguments-differ
|
||||
"""Close the underlying socket connection.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
self.socket = None
|
||||
|
||||
def send(self, request):
|
||||
"""Send data on the underlying socket.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
super().send(request)
|
||||
if not self.socket:
|
||||
raise ConnectionException(str(self))
|
||||
if request:
|
||||
return self.socket.sendto(
|
||||
request, (self.comm_params.host, self.comm_params.port)
|
||||
)
|
||||
return 0
|
||||
|
||||
def recv(self, size):
|
||||
"""Read data from the underlying descriptor.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
super().recv(size)
|
||||
if not self.socket:
|
||||
raise ConnectionException(str(self))
|
||||
return self.socket.recvfrom(size)[0]
|
||||
|
||||
def is_socket_open(self):
|
||||
"""Check if socket is open.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
"""Build a string representation of the connection."""
|
||||
return f"ModbusUdpClient({self.comm_params.host}:{self.comm_params.port})"
|
||||
|
||||
def __repr__(self):
|
||||
"""Return string representation."""
|
||||
return (
|
||||
f"<{self.__class__.__name__} at {hex(id(self))} socket={self.socket}, "
|
||||
f"ipaddr={self.comm_params.host}, port={self.comm_params.port}, timeout={self.comm_params.timeout_connect}>"
|
||||
)
|
||||
Reference in New Issue
Block a user