Files
tac2100_solar_mbus2mqtt/env/lib/python3.11/site-packages/pymodbus/framer/base.py
2025-01-03 15:06:21 +01:00

143 lines
4.3 KiB
Python

"""Framer start."""
# pylint: disable=missing-type-doc
from typing import Any, Dict, Union
from pymodbus.factory import ClientDecoder, ServerDecoder
from pymodbus.logging import Log
# Unit ID, Function Code
BYTE_ORDER = ">"
FRAME_HEADER = "BB"
# Transaction Id, Protocol ID, Length, Unit ID, Function Code
SOCKET_FRAME_HEADER = BYTE_ORDER + "HHH" + FRAME_HEADER
# Function Code
TLS_FRAME_HEADER = BYTE_ORDER + "B"
class ModbusFramer:
"""Base Framer class."""
name = ""
def __init__(
self,
decoder: Union[ClientDecoder, ServerDecoder],
client=None,
) -> None:
"""Initialize a new instance of the framer.
:param decoder: The decoder implementation to use
"""
self.decoder = decoder
self.client = client
self._header: Dict[str, Any] = {
"lrc": "0000",
"len": 0,
"uid": 0x00,
"tid": 0,
"pid": 0,
"crc": b"\x00\x00",
}
self._buffer = b""
def _validate_slave_id(self, slaves: list, single: bool) -> bool:
"""Validate if the received data is valid for the client.
:param slaves: list of slave id for which the transaction is valid
:param single: Set to true to treat this as a single context
:return:
"""
if single:
return True
if 0 in slaves or 0xFF in slaves:
# Handle Modbus TCP slave identifier (0x00 0r 0xFF)
# in asynchronous requests
return True
return self._header["uid"] in slaves
def sendPacket(self, message):
"""Send packets on the bus.
With 3.5char delay between frames
:param message: Message to be sent over the bus
:return:
"""
return self.client.send(message)
def recvPacket(self, size):
"""Receive packet from the bus.
With specified len
:param size: Number of bytes to read
:return:
"""
return self.client.recv(size)
def resetFrame(self):
"""Reset the entire message frame.
This allows us to skip ovver errors that may be in the stream.
It is hard to know if we are simply out of sync or if there is
an error in the stream as we have no way to check the start or
end of the message (python just doesn't have the resolution to
check for millisecond delays).
"""
Log.debug(
"Resetting frame - Current Frame in buffer - {}", self._buffer, ":hex"
)
self._buffer = b""
self._header = {
"lrc": "0000",
"crc": b"\x00\x00",
"len": 0,
"uid": 0x00,
"pid": 0,
"tid": 0,
}
def populateResult(self, result):
"""Populate the modbus result header.
The serial packets do not have any header information
that is copied.
:param result: The response packet
"""
result.slave_id = self._header.get("uid", 0)
result.transaction_id = self._header.get("tid", 0)
result.protocol_id = self._header.get("pid", 0)
def processIncomingPacket(self, data, callback, slave, **kwargs):
"""Process new packet pattern.
This takes in a new request packet, adds it to the current
packet stream, and performs framing on it. That is, checks
for complete messages, and once found, will process all that
exist. This handles the case when we read N + 1 or 1 // N
messages at a time instead of 1.
The processed and decoded messages are pushed to the callback
function to process and send.
:param data: The new packet data
:param callback: The function to send results to
:param slave: Process if slave id matches, ignore otherwise (could be a
list of slave ids (server) or single slave id(client/server))
:param kwargs:
:raises ModbusIOException:
"""
Log.debug("Processing: {}", data, ":hex")
self._buffer += data
if not isinstance(slave, (list, tuple)):
slave = [slave]
single = kwargs.pop("single", False)
self.frameProcessIncomingPacket(single, callback, slave, **kwargs)
def frameProcessIncomingPacket(
self, _single, _callback, _slave, _tid=None, **kwargs
):
"""Process new packet pattern."""