11"""Modbus Request/Response Decoders."""
22from __future__ import annotations
33
4+ import copy
5+
46from pymodbus .exceptions import MessageRegisterException , ModbusException
57from pymodbus .logging import Log
68
1012class DecodePDU :
1113 """Decode pdu requests/responses (server/client)."""
1214
13- _pdu_class_table : set [tuple [type [ModbusPDU ], type [ModbusPDU ]]] = set ()
14- _pdu_sub_class_table : set [tuple [type [ModbusPDU ], type [ModbusPDU ]]] = set ()
15+ pdu_table : dict [int , tuple [type [ModbusPDU ], type [ModbusPDU ]]] = {}
16+ pdu_sub_table : dict [int , dict [int , tuple [type [ModbusPDU ], type [ModbusPDU ]]]] = {}
17+
1518
1619 def __init__ (self , is_server : bool ) -> None :
1720 """Initialize function_tables."""
18- inx = 0 if is_server else 1
19- self .lookup : dict [int , type [ModbusPDU ]] = {cl [inx ].function_code : cl [inx ] for cl in self ._pdu_class_table }
20- self .sub_lookup : dict [int , dict [int , type [ModbusPDU ]]] = {}
21- for f in self ._pdu_sub_class_table :
22- if (function_code := f [inx ].function_code ) not in self .sub_lookup :
23- self .sub_lookup [function_code ] = {f [inx ].sub_function_code : f [inx ]}
24- else :
25- self .sub_lookup [function_code ][f [inx ].sub_function_code ] = f [inx ]
21+ self .pdu_inx = 0 if is_server else 1
2622
2723 def lookupPduClass (self , data : bytes ) -> type [ModbusPDU ] | None :
2824 """Use `function_code` to determine the class of the PDU."""
29- func_code = int (data [1 ])
30- if func_code & 0x80 :
25+ if (func_code := int (data [1 ])) & 0x80 :
3126 return ExceptionResponse
27+ if not (pdu := self .pdu_table .get (func_code , (None , None ))[self .pdu_inx ]):
28+ return None
29+
3230 if func_code == 0x2B : # mei message, sub_function_code is 1 byte
3331 sub_func_code = int (data [2 ])
34- return self .sub_lookup [func_code ].get (sub_func_code , None )
35- if func_code == 0x08 : # diag message, sub_function_code is 2 bytes
32+ elif func_code == 0x08 : # diag message, sub_function_code is 2 bytes
3633 sub_func_code = int .from_bytes (data [2 :4 ], "big" )
37- return self .sub_lookup [func_code ].get (sub_func_code , None )
38- return self .lookup .get (func_code , None )
34+ else :
35+ return pdu
36+ return self .pdu_sub_table [func_code ].get (sub_func_code , (None , None ))[self .pdu_inx ]
3937
4038 def list_function_codes (self ):
4139 """Return list of function codes."""
42- return list (self .lookup )
40+ return list (self .pdu_table )
4341
4442 @classmethod
4543 def add_pdu (cls , req : type [ModbusPDU ], resp : type [ModbusPDU ]):
4644 """Register request/response."""
47- cls ._pdu_class_table . add (( req , resp ) )
45+ cls .pdu_table [ req . function_code ] = ( req , resp )
4846
4947 @classmethod
5048 def add_sub_pdu (cls , req : type [ModbusPDU ], resp : type [ModbusPDU ]):
5149 """Register request/response."""
52- cls ._pdu_sub_class_table .add ((req , resp ))
50+ if req .function_code not in cls .pdu_sub_table :
51+ cls .pdu_sub_table [req .function_code ] = {}
52+ cls .pdu_sub_table [req .function_code ][req .sub_function_code ] = (req , resp )
5353
5454 def register (self , custom_class : type [ModbusPDU ]) -> None :
5555 """Register a function and sub function class with the decoder."""
@@ -59,13 +59,9 @@ def register(self, custom_class: type[ModbusPDU]) -> None:
5959 ". Class needs to be derived from "
6060 "`pymodbus.pdu.ModbusPDU` "
6161 )
62- self .lookup [custom_class .function_code ] = custom_class
63- if custom_class .sub_function_code >= 0 :
64- if custom_class .function_code not in self .sub_lookup :
65- self .sub_lookup [custom_class .function_code ] = {}
66- self .sub_lookup [custom_class .function_code ][
67- custom_class .sub_function_code
68- ] = custom_class
62+ if "pdu_table" not in self .__dict__ :
63+ self .pdu_table = copy .deepcopy (DecodePDU .pdu_table )
64+ self .pdu_table [custom_class .function_code ] = (custom_class , custom_class )
6965
7066 def decode (self , frame : bytes ) -> ModbusPDU | None :
7167 """Decode a frame."""
@@ -74,14 +70,14 @@ def decode(self, frame: bytes) -> ModbusPDU | None:
7470 pdu_exp = ExceptionResponse (function_code & 0x7F )
7571 pdu_exp .decode (frame [1 :])
7672 return pdu_exp
77- if not (pdu_class := self .lookup .get (function_code , None ) ):
73+ if not (pdu_class := self .pdu_table .get (function_code , ( None , None ))[ self . pdu_inx ] ):
7874 Log .debug ("decode PDU failed for function code {}" , function_code )
7975 raise ModbusException (f"Unknown response { function_code } " )
8076 pdu = pdu_class ()
8177 pdu .decode (frame [1 :])
8278 if pdu .sub_function_code >= 0 :
83- lookup = self .sub_lookup .get (pdu .function_code , {})
84- if sub_class := lookup .get (pdu .sub_function_code , None ) :
79+ lookup = self .pdu_sub_table .get (pdu .function_code , {})
80+ if sub_class := lookup .get (pdu .sub_function_code , ( None , None ))[ self . pdu_inx ] :
8581 pdu = sub_class ()
8682 pdu .decode (frame [1 :])
8783 Log .debug ("decoded PDU function_code({} sub {}) -> {} " , pdu .function_code , pdu .sub_function_code , str (pdu ))
0 commit comments