M lazy-lock.json M lua/custom/configs/lspconfig.lua M lua/custom/init.lua A lua/custom/journal.lua A nvim_venv/bin/Activate.ps1 A nvim_venv/bin/activate A nvim_venv/bin/activate.csh A nvim_venv/bin/activate.fish
138 lines
5.0 KiB
Python
138 lines
5.0 KiB
Python
"""Implementation of a Threaded Modbus Server."""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import traceback
|
|
|
|
from pymodbus.exceptions import ModbusIOException, NoSuchSlaveException
|
|
from pymodbus.logging import Log
|
|
from pymodbus.pdu.pdu import ExceptionResponse
|
|
from pymodbus.transaction import TransactionManager
|
|
from pymodbus.transport import CommParams, ModbusProtocol
|
|
|
|
|
|
class ServerRequestHandler(TransactionManager):
|
|
"""Handle client connection."""
|
|
|
|
def __init__(self, owner, trace_packet, trace_pdu, trace_connect):
|
|
"""Initialize."""
|
|
params = CommParams(
|
|
comm_name="server",
|
|
comm_type=owner.comm_params.comm_type,
|
|
reconnect_delay=0.0,
|
|
reconnect_delay_max=0.0,
|
|
timeout_connect=0.0,
|
|
host=owner.comm_params.source_address[0],
|
|
port=owner.comm_params.source_address[1],
|
|
handle_local_echo=owner.comm_params.handle_local_echo,
|
|
)
|
|
self.server = owner
|
|
self.framer = self.server.framer(self.server.decoder)
|
|
self.running = False
|
|
super().__init__(
|
|
params,
|
|
self.framer,
|
|
0,
|
|
True,
|
|
trace_packet,
|
|
trace_pdu,
|
|
trace_connect,
|
|
)
|
|
|
|
def callback_new_connection(self) -> ModbusProtocol:
|
|
"""Call when listener receive new connection request."""
|
|
raise RuntimeError("callback_new_connection should never be called")
|
|
|
|
def callback_connected(self) -> None:
|
|
"""Call when connection is succcesfull."""
|
|
super().callback_connected()
|
|
slaves = self.server.context.slaves()
|
|
if self.server.broadcast_enable:
|
|
if 0 not in slaves:
|
|
slaves.append(0)
|
|
|
|
def callback_disconnected(self, call_exc: Exception | None) -> None:
|
|
"""Call when connection is lost."""
|
|
super().callback_disconnected(call_exc)
|
|
try:
|
|
if call_exc is None:
|
|
Log.debug(
|
|
"Handler for stream [{}] has been canceled", self.comm_params.comm_name
|
|
)
|
|
else:
|
|
Log.debug(
|
|
"Client Disconnection {} due to {}",
|
|
self.comm_params.comm_name,
|
|
call_exc,
|
|
)
|
|
self.running = False
|
|
except Exception as exc: # pylint: disable=broad-except
|
|
Log.error(
|
|
"Datastore unable to fulfill request: {}; {}",
|
|
exc,
|
|
traceback.format_exc(),
|
|
)
|
|
|
|
def callback_data(self, data: bytes, addr: tuple | None = None) -> int:
|
|
"""Handle received data."""
|
|
try:
|
|
used_len = super().callback_data(data, addr)
|
|
except ModbusIOException:
|
|
response = ExceptionResponse(
|
|
40,
|
|
exception_code=ExceptionResponse.ILLEGAL_FUNCTION
|
|
)
|
|
self.server_send(response, 0)
|
|
return(len(data))
|
|
if self.last_pdu:
|
|
if self.is_server:
|
|
self.loop.call_soon(self.handle_later)
|
|
else:
|
|
self.response_future.set_result(True)
|
|
return used_len
|
|
|
|
def handle_later(self):
|
|
"""Change sync (async not allowed in call_soon) to async."""
|
|
asyncio.run_coroutine_threadsafe(self.handle_request(), self.loop)
|
|
|
|
async def handle_request(self):
|
|
"""Handle request."""
|
|
broadcast = False
|
|
if not self.last_pdu:
|
|
return
|
|
try:
|
|
if self.server.broadcast_enable and not self.last_pdu.dev_id:
|
|
broadcast = True
|
|
# if broadcasting then execute on all slave contexts,
|
|
# note response will be ignored
|
|
for dev_id in self.server.context.slaves():
|
|
response = await self.last_pdu.update_datastore(self.server.context[dev_id])
|
|
else:
|
|
context = self.server.context[self.last_pdu.dev_id]
|
|
response = await self.last_pdu.update_datastore(context)
|
|
|
|
except NoSuchSlaveException:
|
|
Log.error("requested slave does not exist: {}", self.last_pdu.dev_id)
|
|
if self.server.ignore_missing_slaves:
|
|
return # the client will simply timeout waiting for a response
|
|
response = ExceptionResponse(0x00, ExceptionResponse.GATEWAY_NO_RESPONSE)
|
|
except Exception as exc: # pylint: disable=broad-except
|
|
Log.error(
|
|
"Datastore unable to fulfill request: {}; {}",
|
|
exc,
|
|
traceback.format_exc(),
|
|
)
|
|
response = ExceptionResponse(0x00, ExceptionResponse.SLAVE_FAILURE)
|
|
# no response when broadcasting
|
|
if not broadcast:
|
|
response.transaction_id = self.last_pdu.transaction_id
|
|
response.dev_id = self.last_pdu.dev_id
|
|
self.server_send(response, self.last_addr)
|
|
|
|
def server_send(self, pdu, addr):
|
|
"""Send message."""
|
|
if not pdu:
|
|
Log.debug("Skipping sending response!!")
|
|
else:
|
|
self.pdu_send(pdu, addr=addr)
|