191 lines
5.3 KiB
Python
191 lines
5.3 KiB
Python
"""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}>"
|
|
)
|