66"""
77from __future__ import annotations
88
9+ import struct
10+
11+ from pymodbus .exceptions import ModbusIOException
12+ from pymodbus .factory import ClientDecoder
13+ from pymodbus .logging import Log
914from pymodbus .message .base import MessageBase
1015
1116
@@ -40,6 +45,13 @@ class MessageRTU(MessageBase):
4045 neither when receiving nor when sending.
4146 """
4247
48+ function_codes : list [int ] = []
49+
50+ @classmethod
51+ def set_legal_function_codes (cls , function_codes : list [int ]):
52+ """Set legal function codes."""
53+ cls .function_codes = function_codes
54+
4355 @classmethod
4456 def generate_crc16_table (cls ) -> list [int ]:
4557 """Generate a crc16 lookup table.
@@ -59,10 +71,116 @@ def generate_crc16_table(cls) -> list[int]:
5971 return result
6072 crc16_table : list [int ] = [0 ]
6173
62- def decode (self , _data : bytes ) -> tuple [int , int , int , bytes ]:
74+ def _legacy_decode (self , callback , slave ): # noqa: C901
75+ """Process new packet pattern."""
76+
77+ def is_frame_ready (self ):
78+ """Check if we should continue decode logic."""
79+ size = self ._header .get ("len" , 0 )
80+ if not size and len (self ._buffer ) > self ._hsize :
81+ try :
82+ self ._header ["uid" ] = int (self ._buffer [0 ])
83+ self ._header ["tid" ] = int (self ._buffer [0 ])
84+ func_code = int (self ._buffer [1 ])
85+ pdu_class = self .decoder .lookupPduClass (func_code )
86+ size = pdu_class .calculateRtuFrameSize (self ._buffer )
87+ self ._header ["len" ] = size
88+
89+ if len (self ._buffer ) < size :
90+ raise IndexError
91+ self ._header ["crc" ] = self ._buffer [size - 2 : size ]
92+ except IndexError :
93+ return False
94+ return len (self ._buffer ) >= size if size > 0 else False
95+
96+ def get_frame_start (self , slaves , broadcast , skip_cur_frame ):
97+ """Scan buffer for a relevant frame start."""
98+ start = 1 if skip_cur_frame else 0
99+ if (buf_len := len (self ._buffer )) < 4 :
100+ return False
101+ for i in range (start , buf_len - 3 ): # <slave id><function code><crc 2 bytes>
102+ if not broadcast and self ._buffer [i ] not in slaves :
103+ continue
104+ if (
105+ self ._buffer [i + 1 ] not in self .function_codes
106+ and (self ._buffer [i + 1 ] - 0x80 ) not in self .function_codes
107+ ):
108+ continue
109+ if i :
110+ self ._buffer = self ._buffer [i :] # remove preceding trash.
111+ return True
112+ if buf_len > 3 :
113+ self ._buffer = self ._buffer [- 3 :]
114+ return False
115+
116+ def check_frame (self ):
117+ """Check if the next frame is available."""
118+ try :
119+ self ._header ["uid" ] = int (self ._buffer [0 ])
120+ self ._header ["tid" ] = int (self ._buffer [0 ])
121+ func_code = int (self ._buffer [1 ])
122+ pdu_class = self .decoder .lookupPduClass (func_code )
123+ size = pdu_class .calculateRtuFrameSize (self ._buffer )
124+ self ._header ["len" ] = size
125+
126+ if len (self ._buffer ) < size :
127+ raise IndexError
128+ self ._header ["crc" ] = self ._buffer [size - 2 : size ]
129+ frame_size = self ._header ["len" ]
130+ data = self ._buffer [: frame_size - 2 ]
131+ crc = self ._header ["crc" ]
132+ crc_val = (int (crc [0 ]) << 8 ) + int (crc [1 ])
133+ return MessageRTU .check_CRC (data , crc_val )
134+ except (IndexError , KeyError , struct .error ):
135+ return False
136+
137+ self ._buffer = b'' # pylint: disable=attribute-defined-outside-init
138+ broadcast = not slave [0 ]
139+ skip_cur_frame = False
140+ while get_frame_start (self , slave , broadcast , skip_cur_frame ):
141+ self ._header : dict = {"uid" : 0x00 , "len" : 0 , "crc" : b"\x00 \x00 " } # pylint: disable=attribute-defined-outside-init
142+ if not is_frame_ready (self ):
143+ Log .debug ("Frame - not ready" )
144+ break
145+ if not check_frame (self ):
146+ Log .debug ("Frame check failed, ignoring!!" )
147+ # x = self._buffer
148+ # self.resetFrame()
149+ # self._buffer = x
150+ skip_cur_frame = True
151+ continue
152+ start = 0x01 # self._hsize
153+ end = self ._header ["len" ] - 2
154+ buffer = self ._buffer [start :end ]
155+ if end > 0 :
156+ Log .debug ("Getting Frame - {}" , buffer , ":hex" )
157+ data = buffer
158+ else :
159+ data = b""
160+ if (result := ClientDecoder ().decode (data )) is None :
161+ raise ModbusIOException ("Unable to decode request" )
162+ result .slave_id = self ._header ["uid" ]
163+ result .transaction_id = self ._header ["tid" ]
164+ self ._buffer = self ._buffer [self ._header ["len" ] :] # pylint: disable=attribute-defined-outside-init
165+ Log .debug ("Frame advanced, resetting header!!" )
166+ callback (result ) # defer or push to a thread?
167+
168+
169+ def decode (self , data : bytes ) -> tuple [int , int , int , bytes ]:
63170 """Decode message."""
171+ resp = None
172+ if len (data ) < 4 :
173+ return 0 , 0 , 0 , b''
174+
175+ def callback (result ):
176+ """Set result."""
177+ nonlocal resp
178+ resp = result
179+
180+ self ._legacy_decode (callback , self .device_ids )
64181 return 0 , 0 , 0 , b''
65182
183+
66184 def encode (self , data : bytes , device_id : int , _tid : int ) -> bytes :
67185 """Decode message."""
68186 packet = device_id .to_bytes (1 ,'big' ) + data
0 commit comments