Dane Sabo 3299181c70 Auto sync: 2025-09-02 22:47:53 (10335 files changed)
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
2025-09-02 22:47:53 -04:00

123 lines
6.9 KiB
Python

"""Modbus Request/Response Decoders."""
from __future__ import annotations
import pymodbus.pdu.bit_message as bit_msg
import pymodbus.pdu.diag_message as diag_msg
import pymodbus.pdu.file_message as file_msg
import pymodbus.pdu.mei_message as mei_msg
import pymodbus.pdu.other_message as o_msg
import pymodbus.pdu.pdu as base
import pymodbus.pdu.register_message as reg_msg
from pymodbus.exceptions import MessageRegisterException, ModbusException
from pymodbus.logging import Log
class DecodePDU:
"""Decode pdu requests/responses (server/client)."""
_pdu_class_table: set[tuple[type[base.ModbusPDU], type[base.ModbusPDU]]] = {
(reg_msg.ReadHoldingRegistersRequest, reg_msg.ReadHoldingRegistersResponse),
(bit_msg.ReadDiscreteInputsRequest, bit_msg.ReadDiscreteInputsResponse),
(reg_msg.ReadInputRegistersRequest, reg_msg.ReadInputRegistersResponse),
(bit_msg.ReadCoilsRequest, bit_msg.ReadCoilsResponse),
(bit_msg.WriteMultipleCoilsRequest, bit_msg.WriteMultipleCoilsResponse),
(reg_msg.WriteMultipleRegistersRequest, reg_msg.WriteMultipleRegistersResponse),
(reg_msg.WriteSingleRegisterRequest, reg_msg.WriteSingleRegisterResponse),
(bit_msg.WriteSingleCoilRequest, bit_msg.WriteSingleCoilResponse),
(reg_msg.ReadWriteMultipleRegistersRequest, reg_msg.ReadWriteMultipleRegistersResponse),
(diag_msg.DiagnosticBase, diag_msg.DiagnosticBase),
(o_msg.ReadExceptionStatusRequest, o_msg.ReadExceptionStatusResponse),
(o_msg.GetCommEventCounterRequest, o_msg.GetCommEventCounterResponse),
(o_msg.GetCommEventLogRequest, o_msg.GetCommEventLogResponse),
(o_msg.ReportSlaveIdRequest, o_msg.ReportSlaveIdResponse),
(file_msg.ReadFileRecordRequest, file_msg.ReadFileRecordResponse),
(file_msg.WriteFileRecordRequest, file_msg.WriteFileRecordResponse),
(reg_msg.MaskWriteRegisterRequest, reg_msg.MaskWriteRegisterResponse),
(file_msg.ReadFifoQueueRequest, file_msg.ReadFifoQueueResponse),
(mei_msg.ReadDeviceInformationRequest, mei_msg.ReadDeviceInformationResponse),
}
_pdu_sub_class_table: set[tuple[type[base.ModbusPDU], type[base.ModbusPDU]]] = {
(diag_msg.ReturnQueryDataRequest, diag_msg.ReturnQueryDataResponse),
(diag_msg.RestartCommunicationsOptionRequest, diag_msg.RestartCommunicationsOptionResponse),
(diag_msg.ReturnDiagnosticRegisterRequest, diag_msg.ReturnDiagnosticRegisterResponse),
(diag_msg.ChangeAsciiInputDelimiterRequest, diag_msg.ChangeAsciiInputDelimiterResponse),
(diag_msg.ForceListenOnlyModeRequest, diag_msg.ForceListenOnlyModeResponse),
(diag_msg.ClearCountersRequest, diag_msg.ClearCountersResponse),
(diag_msg.ReturnBusMessageCountRequest, diag_msg.ReturnBusMessageCountResponse),
(diag_msg.ReturnBusCommunicationErrorCountRequest, diag_msg.ReturnBusCommunicationErrorCountResponse),
(diag_msg.ReturnBusExceptionErrorCountRequest, diag_msg.ReturnBusExceptionErrorCountResponse),
(diag_msg.ReturnSlaveMessageCountRequest, diag_msg.ReturnSlaveMessageCountResponse),
(diag_msg.ReturnSlaveNoResponseCountRequest, diag_msg.ReturnSlaveNoResponseCountResponse),
(diag_msg.ReturnSlaveNAKCountRequest, diag_msg.ReturnSlaveNAKCountResponse),
(diag_msg.ReturnSlaveBusyCountRequest, diag_msg.ReturnSlaveBusyCountResponse),
(diag_msg.ReturnSlaveBusCharacterOverrunCountRequest, diag_msg.ReturnSlaveBusCharacterOverrunCountResponse),
(diag_msg.ReturnIopOverrunCountRequest, diag_msg.ReturnIopOverrunCountResponse),
(diag_msg.ClearOverrunCountRequest, diag_msg.ClearOverrunCountResponse),
(diag_msg.GetClearModbusPlusRequest, diag_msg.GetClearModbusPlusResponse),
(mei_msg.ReadDeviceInformationRequest, mei_msg.ReadDeviceInformationResponse),
}
def __init__(self, is_server: bool) -> None:
"""Initialize function_tables."""
inx = 0 if is_server else 1
self.lookup: dict[int, type[base.ModbusPDU]] = {cl[inx].function_code: cl[inx] for cl in self._pdu_class_table}
self.sub_lookup: dict[int, dict[int, type[base.ModbusPDU]]] = {}
for f in self._pdu_sub_class_table:
if (function_code := f[inx].function_code) not in self.sub_lookup:
self.sub_lookup[function_code] = {f[inx].sub_function_code: f[inx]}
else:
self.sub_lookup[function_code][f[inx].sub_function_code] = f[inx]
def lookupPduClass(self, data: bytes) -> type[base.ModbusPDU] | None:
"""Use `function_code` to determine the class of the PDU."""
func_code = int(data[1])
if func_code & 0x80:
return base.ExceptionResponse
if func_code == 0x2B: # mei message, sub_function_code is 1 byte
sub_func_code = int(data[2])
return self.sub_lookup[func_code].get(sub_func_code, None)
if func_code == 0x08: # diag message, sub_function_code is 2 bytes
sub_func_code = int(data[3])
return self.sub_lookup[func_code].get(sub_func_code, None)
return self.lookup.get(func_code, None)
def register(self, custom_class: type[base.ModbusPDU]) -> None:
"""Register a function and sub function class with the decoder."""
if not issubclass(custom_class, base.ModbusPDU):
raise MessageRegisterException(
f'"{custom_class.__class__.__name__}" is Not a valid Modbus Message'
". Class needs to be derived from "
"`pymodbus.pdu.ModbusPDU` "
)
self.lookup[custom_class.function_code] = custom_class
if custom_class.sub_function_code >= 0:
if custom_class.function_code not in self.sub_lookup:
self.sub_lookup[custom_class.function_code] = {}
self.sub_lookup[custom_class.function_code][
custom_class.sub_function_code
] = custom_class
def decode(self, frame: bytes) -> base.ModbusPDU | None:
"""Decode a frame."""
try:
if (function_code := int(frame[0])) > 0x80:
pdu_exp = base.ExceptionResponse(function_code & 0x7F)
pdu_exp.decode(frame[1:])
return pdu_exp
if not (pdu_type := self.lookup.get(function_code, None)):
Log.debug("decode PDU failed for function code {}", function_code)
raise ModbusException(f"Unknown response {function_code}")
pdu = pdu_type()
pdu.decode(frame[1:])
Log.debug("decoded PDU function_code({} sub {}) -> {} ", pdu.function_code, pdu.sub_function_code, str(pdu))
if pdu.sub_function_code >= 0:
lookup = self.sub_lookup.get(pdu.function_code, {})
if subtype := lookup.get(pdu.sub_function_code, None):
pdu.__class__ = subtype
return pdu
except (ModbusException, ValueError, IndexError) as exc:
Log.warning("Unable to decode frame {}", exc)
return None