"""
S7 error handling and exception classes.
Maps S7 error codes to Python exceptions with meaningful messages.
"""
from typing import Optional, Callable, Any, Hashable
from functools import cache
[docs]
class S7Error(Exception):
"""Base exception for all S7 protocol errors."""
def __init__(self, message: str, error_code: Optional[int] = None):
super().__init__(message)
self.error_code = error_code
[docs]
class S7ConnectionError(S7Error):
"""Raised when connection to S7 device fails."""
pass
[docs]
class S7ProtocolError(S7Error):
"""Raised when S7 protocol communication fails."""
pass
[docs]
class S7TimeoutError(S7Error):
"""Raised when S7 operation times out."""
pass
[docs]
class S7AuthenticationError(S7Error):
"""Raised when S7 authentication fails."""
pass
# S7 client error codes
s7_client_errors = {
0x00100000: "errNegotiatingPDU",
0x00200000: "errCliInvalidParams",
0x00300000: "errCliJobPending",
0x00400000: "errCliTooManyItems",
0x00500000: "errCliInvalidWordLen",
0x00600000: "errCliPartialDataWritten",
0x00700000: "errCliSizeOverPDU",
0x00800000: "errCliInvalidPlcAnswer",
0x00900000: "errCliAddressOutOfRange",
0x00A00000: "errCliInvalidTransportSize",
0x00B00000: "errCliWriteDataSizeMismatch",
0x00C00000: "errCliItemNotAvailable",
0x00D00000: "errCliInvalidValue",
0x00E00000: "errCliCannotStartPLC",
0x00F00000: "errCliAlreadyRun",
0x01000000: "errCliCannotStopPLC",
0x01100000: "errCliCannotCopyRamToRom",
0x01200000: "errCliCannotCompress",
0x01300000: "errCliAlreadyStop",
0x01400000: "errCliFunNotAvailable",
0x01500000: "errCliUploadSequenceFailed",
0x01600000: "errCliInvalidDataSizeRecvd",
0x01700000: "errCliInvalidBlockType",
0x01800000: "errCliInvalidBlockNumber",
0x01900000: "errCliInvalidBlockSize",
0x01A00000: "errCliDownloadSequenceFailed",
0x01B00000: "errCliInsertRefused",
0x01C00000: "errCliDeleteRefused",
0x01D00000: "errCliNeedPassword",
0x01E00000: "errCliInvalidPassword",
0x01F00000: "errCliNoPasswordToSetOrClear",
0x02000000: "errCliJobTimeout",
0x02100000: "errCliPartialDataRead",
0x02200000: "errCliBufferTooSmall",
0x02300000: "errCliFunctionRefused",
0x02400000: "errCliDestroying",
0x02500000: "errCliInvalidParamNumber",
0x02600000: "errCliCannotChangeParam",
}
isotcp_errors = {
0x00010000: "errIsoConnect",
0x00020000: "errIsoDisconnect",
0x00030000: "errIsoInvalidPDU",
0x00040000: "errIsoInvalidDataSize",
0x00050000: "errIsoNullPointer",
0x00060000: "errIsoShortPacket",
0x00070000: "errIsoTooManyFragments",
0x00080000: "errIsoPduOverflow",
0x00090000: "errIsoSendPacket",
0x000A0000: "errIsoRecvPacket",
0x000B0000: "errIsoInvalidParams",
}
tcp_errors = {
0x00000001: "evcServerStarted",
0x00000002: "evcServerStopped",
0x00000004: "evcListenerCannotStart",
0x00000008: "evcClientAdded",
0x00000010: "evcClientRejected",
0x00000020: "evcClientNoRoom",
0x00000040: "evcClientException",
0x00000080: "evcClientDisconnected",
0x00000100: "evcClientTerminated",
0x00000200: "evcClientsDropped",
}
s7_server_errors = {
0x00100000: "errSrvCannotStart",
0x00200000: "errSrvDBNullPointer",
0x00300000: "errSrvAreaAlreadyExists",
0x00400000: "errSrvUnknownArea",
0x00500000: "errSrvInvalidParams",
0x00600000: "errSrvTooManyDB",
0x00700000: "errSrvInvalidParamNumber",
0x00800000: "errSrvCannotChangeParam",
}
# S7 protocol-level error codes (from Wireshark S7 dissector)
# These cover USERDATA parameter errors, protocol errors, and resource errors
# that occur during real PLC communication.
# Source: https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-s7comm.c
S7_PROTOCOL_ERROR_CODES = {
0x0000: "No error",
0x0110: "Invalid block number",
0x0111: "Invalid request length",
0x0112: "Invalid parameter",
0x0113: "Invalid block type",
0x0114: "Block not found",
0x0115: "Block already exists",
0x0116: "Block is write-protected",
0x0117: "The block/operating system update is too large",
0x0118: "Invalid block number",
0x0119: "Incorrect password entered",
0x011A: "PG resource error",
0x011B: "PLC resource error",
0x011C: "Protocol error",
0x011D: "Too many blocks (module-related restriction)",
0x011E: "There is no longer a connection to the database, or S7DOS handle is invalid",
0x011F: "Result buffer too small",
0x0120: "End of block list",
0x0140: "Insufficient memory available",
0x0141: "Job cannot be processed because of a lack of resources",
0x8001: "The requested service cannot be performed while the block is in the current status",
0x8003: "S7 protocol error: Error occurred while transferring the block",
0x8100: "Application, general error: Service unknown to remote module",
0x8104: "This service is not implemented on the module or a frame error was reported",
0x8204: "The type specification for the object is inconsistent",
0x8205: "A copied block already exists and is not linked",
0x8301: "Insufficient memory space or work memory on the module, or specified storage medium not accessible",
0x8302: "Too few resources available or the processor resources are not available",
0x8304: "No further parallel upload possible. There is a resource bottleneck",
0x8305: "Function not available",
0x8306: "Insufficient work memory (for copying, linking, loading AWP)",
0x8307: "Not enough retentive work memory (for copying, linking, loading AWP)",
0x8401: "S7 protocol error: Invalid service sequence (for example, loading or uploading a block)",
0x8402: "Service cannot execute owing to status of the addressed object",
0x8404: "S7 protocol: The function cannot be performed",
0x8405: "Remote block is in DISABLE state (CFB). The function cannot be performed",
0x8500: "S7 protocol error: Wrong frames",
0x8503: "Alarm from the module: Service canceled prematurely",
0x8701: "Error addressing the object on the communications partner (for example, area length error)",
0x8702: "The requested service is not supported by the module",
0x8703: "Access to object refused",
0x8704: "Access error: Object damaged",
0xD001: "Protocol error: Illegal job number",
0xD002: "Parameter error: Illegal job variant",
0xD003: "Parameter error: Debugging function not supported by module",
0xD004: "Parameter error: Illegal job status",
0xD005: "Parameter error: Illegal job termination",
0xD006: "Parameter error: Illegal link disconnection ID",
0xD007: "Parameter error: Illegal number of buffer elements",
0xD008: "Parameter error: Illegal scan rate",
0xD009: "Parameter error: Illegal number of executions",
0xD00A: "Parameter error: Illegal trigger event",
0xD00B: "Parameter error: Illegal trigger condition",
0xD011: "Parameter error in path of the call environment: Block does not exist",
0xD012: "Parameter error: Wrong address in block",
0xD014: "Parameter error: Block being deleted/overwritten",
0xD015: "Parameter error: Illegal tag address",
0xD016: "Parameter error: Test jobs not possible, because of errors in user program",
0xD017: "Parameter error: Illegal trigger number",
0xD025: "Parameter error: Invalid path",
0xD026: "Parameter error: Illegal access type",
0xD027: "Parameter error: This number of data blocks is not permitted",
0xD031: "Internal protocol error",
0xD032: "Parameter error: Wrong result buffer length",
0xD033: "Protocol error: Wrong job length",
0xD03F: "Coding error: Error in parameter section (for example, reserve bytes not equal to 0)",
0xD041: "Data error: Illegal status list ID",
0xD042: "Data error: Illegal tag address",
0xD043: "Data error: Referenced job not found, check job data",
0xD044: "Data error: Illegal tag value, check job data",
0xD045: "Data error: Exiting the ODIS control is not allowed in HOLD",
0xD046: "Data error: Illegal measuring stage during run-time measurement",
0xD047: "Data error: Illegal hierarchy in 'Read job list'",
0xD048: "Data error: Illegal deletion ID in 'Delete job'",
0xD049: "Invalid substitute ID in 'Replace job'",
0xD04A: "Error executing 'program status'",
0xD05F: "Coding error: Error in data section (for example, reserve bytes not equal to 0, ...)",
0xD061: "Resource error: No memory space for job",
0xD062: "Resource error: Job list full",
0xD063: "Resource error: Trigger event occupied",
0xD064: "Resource error: Not enough memory space for one result buffer element",
0xD065: "Resource error: Not enough memory space for several result buffer elements",
0xD066: "Resource error: The timer available for run-time measurement is occupied by another job",
0xD067: "Resource error: Too many 'modify tag' jobs active (in particular multi-processor operation)",
0xD081: "Function not permitted in current mode",
0xD082: "Mode error: Cannot exit HOLD mode",
0xD0A1: "Function not permitted in current protection level",
0xD0A2: "Function not possible at present, because a function is running that modifies memory",
0xD0A3: "Too many 'modify tag' jobs active on the I/O (in particular multi-processor operation)",
0xD0A4: "'Forcing' has already been established",
0xD0A5: "Referenced job not found",
0xD0A6: "Job cannot be disabled/enabled",
0xD0A7: "Job cannot be deleted, for example because it is currently being read",
0xD0A8: "Job cannot be replaced, for example because it is currently being read or deleted",
0xD0A9: "Job cannot be read, for example because it is currently being deleted",
0xD0AA: "Time limit exceeded in processing operation",
0xD0AB: "Invalid job parameters in process operation",
0xD0AC: "Invalid job data in process operation",
0xD0AD: "Operating mode already set",
0xD0AE: "The job was set up over a different connection and can only be handled over this connection",
0xD0C1: "At least one error has been detected while accessing the tag(s)",
0xD0C2: "Change to STOP/HOLD mode",
0xD0C3: "At least one error was detected while accessing the tag(s). Mode change to STOP/HOLD",
0xD0C4: "Timeout during run-time measurement",
0xD0C5: "Display of block stack inconsistent, because blocks were deleted/reloaded",
0xD0C6: "Job was automatically deleted as the jobs it referenced have been deleted",
0xD0C7: "The job was automatically deleted because STOP mode was exited",
0xD0C8: "'Block status' aborted because of inconsistencies between test job and running program",
0xD0C9: "Exit the status area by resetting OB90",
0xD0CA: "Exiting the status range by resetting OB90 and access error reading tags before exiting",
0xD0CB: "The output disable for the peripheral outputs has been activated again",
0xD0CC: "The amount of data for the debugging functions is restricted by the time limit",
0xD201: "Syntax error in block name",
0xD202: "Syntax error in function parameters",
0xD205: "Linked block already exists in RAM: Conditional copying is not possible",
0xD206: "Linked block already exists in EPROM: Conditional copying is not possible",
0xD208: "Maximum number of copied (not linked) blocks on module exceeded",
0xD209: "(At least) one of the given blocks not found on the module",
0xD20A: "The maximum number of blocks that can be linked with one job was exceeded",
0xD20B: "The maximum number of blocks that can be deleted with one job was exceeded",
0xD20C: "OB cannot be copied because the associated priority class does not exist",
0xD20D: "SDB cannot be interpreted (for example, unknown number)",
0xD20E: "No (further) block available",
0xD20F: "Module-specific maximum block size exceeded",
0xD210: "Invalid block number",
0xD212: "Incorrect header attribute (run-time relevant)",
0xD213: "Too many SDBs. Note the restrictions on the module being used",
0xD216: "Invalid user program - reset module",
0xD217: "Protection level specified in module properties not permitted",
0xD218: "Incorrect attribute (active/passive)",
0xD219: "Incorrect block lengths (for example, incorrect length of first section or of the whole block)",
0xD21A: "Incorrect local data length or write-protection code faulty",
0xD21B: "Module cannot compress or compression was interrupted early",
0xD21D: "The volume of dynamic project data transferred is illegal",
0xD21E: "Unable to assign parameters to a module (such as FM, CP). The system data could not be linked",
0xD220: "Invalid programming language. Note the restrictions on the module being used",
0xD221: "The system data for connections or routing are not valid",
0xD222: "The system data of the global data definition contain invalid parameters",
0xD223: "Error in instance data block for communication function block or maximum number of instance DBs exceeded",
0xD224: "The SCAN system data block contains invalid parameters",
0xD225: "The DP system data block contains invalid parameters",
0xD226: "A structural error occurred in a block",
0xD230: "A structural error occurred in a block",
0xD231: "At least one loaded OB cannot be copied because the associated priority class does not exist",
0xD232: "At least one block number of a loaded block is illegal",
0xD234: "Block exists twice in the specified memory medium or in the job",
0xD235: "The block contains an incorrect checksum",
0xD236: "The block does not contain a checksum",
0xD237: "You are about to load the block twice, i.e. a block with the same time stamp already exists on the CPU",
0xD238: "At least one of the blocks specified is not a DB",
0xD239: "At least one of the DBs specified is not available as a linked variant in the load memory",
0xD23A: "At least one of the specified DBs is considerably different from the copied and linked variant",
0xD240: "Coordination rules violated",
0xD241: "The function is not permitted in the current protection level",
0xD242: "Protection violation while processing F blocks",
0xD250: "Update and module ID or version do not match",
0xD251: "Incorrect sequence of operating system components",
0xD252: "Checksum error",
0xD253: "No executable loader available; update only possible using a memory card",
0xD254: "Storage error in operating system",
0xD280: "Error compiling block in S7-300 CPU",
0xD2A1: "Another block function or a trigger on a block is active",
0xD2A2: "A trigger is active on a block. Complete the debugging function first",
0xD2A3: "The block is not active (linked), the block is occupied or the block is currently marked for deletion",
0xD2A4: "The block is already being processed by another block function",
0xD2A6: "It is not possible to save and change the user program simultaneously",
0xD2A7: "The block has the attribute 'unlinked' or is not processed",
0xD2A8: "An active debugging function is preventing parameters from being assigned to the CPU",
0xD2A9: "New parameters are being assigned to the CPU",
0xD2AA: "New parameters are currently being assigned to the modules",
0xD2AB: "The dynamic configuration limits are currently being changed",
0xD2AC: "A running active or deactivate assignment (SFC 12) is temporarily preventing R-KiR process",
0xD2B0: "An error occurred while configuring in RUN (CiR)",
0xD2C0: "The maximum number of technological objects has been exceeded",
0xD2C1: "The same technology data block already exists on the module",
0xD2C2: "Downloading the user program or downloading the hardware configuration is not possible",
0xD401: "Information function unavailable",
0xD402: "Information function unavailable",
0xD403: "Service has already been logged on/off (Diagnostics/PMC)",
0xD404: "Maximum number of nodes reached. No more logons possible for diagnostics/PMC",
0xD405: "Service not supported or syntax error in function parameters",
0xD406: "Required information currently unavailable",
0xD407: "Diagnostics error occurred",
0xD408: "Update aborted",
0xD409: "Error on DP bus",
0xD601: "Syntax error in function parameter",
0xD602: "Incorrect password entered",
0xD603: "The connection has already been legitimized",
0xD604: "The connection has already been enabled",
0xD605: "Legitimization not possible because password does not exist",
0xD801: "At least one tag address is invalid",
0xD802: "Specified job does not exist",
0xD803: "Illegal job status",
0xD804: "Illegal cycle time (illegal time base or multiple)",
0xD805: "No more cyclic read jobs can be set up",
0xD806: "The referenced job is in a state in which the requested function cannot be performed",
0xD807: "Function aborted due to overload, meaning executing the read cycle takes longer than the set scan cycle time",
0xDC01: "Date and/or time invalid",
0xE201: "CPU is already the master",
0xE202: "Connect and update not possible due to different user program in flash module",
0xE203: "Connect and update not possible due to different firmware",
0xE204: "Connect and update not possible due to different memory configuration",
0xE205: "Connect/update aborted due to synchronization error",
0xE206: "Connect/update denied due to coordination violation",
0xEF01: "S7 protocol error: Error at ID2; only 00H permitted in job",
0xEF02: "S7 protocol error: Error at ID2; set of resources does not exist",
}
[docs]
def get_protocol_error_message(code: int) -> str:
"""Get human-readable error message for an S7 protocol-level error code.
These are the error codes found in USERDATA parameter sections and
S7 header error_class/error_code fields. They are distinct from the
higher-level client/ISO error codes.
Args:
code: S7 protocol error code (e.g., 0xD401, 0x8104)
Returns:
Human-readable error description, or a hex-formatted unknown message.
"""
return S7_PROTOCOL_ERROR_CODES.get(code, f"Unknown protocol error: {code:#06x}")
[docs]
class S7StalePacketError(S7ProtocolError):
"""Raised when a response has an old/stale PDU reference number."""
pass
[docs]
class S7PacketLostError(S7ProtocolError):
"""Raised when a response PDU reference is ahead of expected (packet loss)."""
pass
# Combined error dictionaries
client_errors = s7_client_errors.copy()
client_errors.update(isotcp_errors)
client_errors.update(tcp_errors)
server_errors = s7_server_errors.copy()
server_errors.update(isotcp_errors)
server_errors.update(tcp_errors)
# All error codes combined
S7_ERROR_CODES = {
0x00000000: "Success",
**s7_client_errors,
**isotcp_errors,
**s7_server_errors,
}
[docs]
def get_error_message(error_code: int) -> str:
"""Get human-readable error message for S7 error code."""
return S7_ERROR_CODES.get(error_code, f"Unknown error: {error_code:#08x}")
[docs]
@cache
def error_text(error: int, context: str = "client") -> str:
"""Returns a textual explanation of a given error number.
Args:
error: an error integer
context: context in which is called from, server, client or partner
Returns:
The error message as a string.
"""
errors = {"client": client_errors, "server": server_errors, "partner": client_errors}
error_dict = errors.get(context, client_errors)
return error_dict.get(error, f"Unknown error: {error:#08x}")
[docs]
def check_error(code: int, context: str = "client") -> None:
"""Check if the error code is set. If so, raise an appropriate exception.
Args:
code: error code number.
context: context in which is called.
Raises:
S7ConnectionError: for connection-related errors
S7TimeoutError: for timeout errors
S7ProtocolError: for protocol errors
RuntimeError: for other errors (backwards compatibility)
"""
if code == 0:
return
message = error_text(code, context)
# Map to specific exception types based on error code patterns
if code in [0x00010000, 0x00020000]: # ISO connect/disconnect errors
raise S7ConnectionError(message, code)
elif code == 0x02000000: # Job timeout
raise S7TimeoutError(message, code)
elif code in isotcp_errors:
raise S7ConnectionError(message, code)
else:
# Use RuntimeError for backwards compatibility with existing code
raise RuntimeError(message)
[docs]
def error_wrap(context: str) -> Callable[..., Callable[..., None]]:
"""Decorator that parses an S7 error code returned by the decorated function."""
def middle(func: Callable[..., int]) -> Any:
def inner(*args: tuple[Any, ...], **kwargs: dict[Hashable, Any]) -> None:
code = func(*args, **kwargs)
check_error(code, context=context)
return inner
return middle