From 81bcc742d767633930ae2567618a1fa3812542ad Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 1 May 2025 17:05:00 +0200 Subject: [PATCH 1/7] Revert "[ModelicaSystem] remove OMCSessionException for now" This reverts commit 2c3ab3622db6321a3f5cf05fb8b3fa341f8147f5. --- OMPython/ModelicaSystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 47f86d75..356c2602 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -425,7 +425,7 @@ def getContinuous(self, names=None): # 4 try: value = self.getSolutions(i) self.continuouslist[i] = value[0][-1] - except Exception as ex: + except OMCSessionException as ex: raise ModelicaSystemError(f"{i} could not be computed") from ex return self.continuouslist From f67f7406084724f6132b6ee0b2bc770f052e8b91 Mon Sep 17 00:00:00 2001 From: syntron Date: Sat, 26 Apr 2025 21:51:02 +0200 Subject: [PATCH 2/7] [OMCSessionZMQ] allways check for errors if using sendExpression() needs the preparation / additional changes in OMCSession* and ModelicaSystem --- OMPython/OMCSession.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 98ae8461..9cc86421 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -549,6 +549,17 @@ def sendExpression(self, command, parsed=True): return None else: result = self._omc.recv_string() + + # allways check for error + self._omc.send_string("getErrorString()", flags=zmq.NOBLOCK) + error_raw = self._omc.recv_string() + error_str = om_parser_typed(error_raw) + if error_str: + if "Error" in error_str: + raise OMCSessionException(f"OM Error for 'sendExpression({command}, {parsed})': {error_str}") + else: + logger.warning(f"[OM]: {error_str}") + if parsed is True: try: return om_parser_typed(result) From b7fc5b29304a2fcfca931bab7f4534cc1841de1b Mon Sep 17 00:00:00 2001 From: syntron Date: Mon, 28 Apr 2025 15:53:23 +0200 Subject: [PATCH 3/7] [ModelicaSystem] exception handling for sendExpression() --- OMPython/ModelicaSystem.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 356c2602..559b4385 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -46,7 +46,7 @@ from dataclasses import dataclass from typing import Optional -from OMPython.OMCSession import OMCSessionZMQ +from OMPython.OMCSession import OMCSessionZMQ, OMCSessionException # define logger using the current module name as ID logger = logging.getLogger(__name__) @@ -332,8 +332,14 @@ def buildModel(self, variableFilter=None): self.xmlparse() def sendExpression(self, expr, parsed=True): - logger.debug("sendExpression(%r, %r)", expr, parsed) - return self.getconn.sendExpression(expr, parsed) + try: + retval = self.getconn.sendExpression(expr, parsed) + except OMCSessionException as ex: + raise ModelicaSystemError(f"Error executing {repr(expr)}") from ex + + logger.debug(f"Result of executing {repr(expr)}: {repr(retval)}") + + return retval # request to OMC def requestApi(self, apiName, entity=None, properties=None): # 2 From b2e4cb315e897563a9126f893b8ed27d819913d8 Mon Sep 17 00:00:00 2001 From: syntron Date: Tue, 29 Apr 2025 20:50:45 +0200 Subject: [PATCH 4/7] [ModelicaSystem] remove last call to getErrorString() this is handled in OMCSessionZMQ.sendExpression() --- OMPython/ModelicaSystem.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 559b4385..7d65e393 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -1099,8 +1099,7 @@ def linearize(self, lintime: Optional[float] = None, simflags: Optional[str] = N linearFile = pathlib.Path(f'linear_{self.modelName}.py') if not linearFile.exists(): - errormsg = self.sendExpression("getErrorString()") - raise ModelicaSystemError(f"Linearization failed: {linearFile} not found: {errormsg}") + raise ModelicaSystemError(f"Linearization failed: {linearFile} not found!") # this function is called from the generated python code linearized_model.py at runtime, # to improve the performance by directly reading the matrices A, B, C and D from the julia code and avoid building the linearized modelica model From efc1d60cd953eb5b707535b70def517e9ac9a6a4 Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 7 May 2025 09:24:26 +0200 Subject: [PATCH 5/7] [OMCSessionZMQ] use 'getMessagesStringInternal()' to check for OMC errors for each command using sendExpression() --- OMPython/OMCSession.py | 67 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 9cc86421..3795a8e4 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -41,6 +41,7 @@ import os import pathlib import psutil +import re import signal import subprocess import sys @@ -325,6 +326,9 @@ def __init__(self, timeout=10.00, # connect to the running omc instance using ZMQ self._connect_to_omc(timeout) + self._re_log_entries = None + self._re_log_raw = None + def __del__(self): try: self.sendExpression("quit()") @@ -550,15 +554,60 @@ def sendExpression(self, command, parsed=True): else: result = self._omc.recv_string() - # allways check for error - self._omc.send_string("getErrorString()", flags=zmq.NOBLOCK) - error_raw = self._omc.recv_string() - error_str = om_parser_typed(error_raw) - if error_str: - if "Error" in error_str: - raise OMCSessionException(f"OM Error for 'sendExpression({command}, {parsed})': {error_str}") - else: - logger.warning(f"[OM]: {error_str}") + if command == "getErrorString()": + # no error handling if 'getErrorString()' is called + pass + elif command == "getMessagesStringInternal()": + # no error handling if 'getMessagesStringInternal()' is called; parsing NOT possible! + if parsed: + logger.warning("Result of 'getMessagesStringInternal()' cannot be parsed - set parsed to False!") + parsed = False + else: + # allways check for error + self._omc.send_string('getMessagesStringInternal()', flags=zmq.NOBLOCK) + error_raw = self._omc.recv_string() + # run error handling only if there is something to check + if error_raw != "{}\n": + if not self._re_log_entries: + self._re_log_entries = re.compile(pattern=r'record OpenModelica\.Scripting\.ErrorMessage' + '(.*?)' + r'end OpenModelica\.Scripting\.ErrorMessage;', + flags=re.MULTILINE | re.DOTALL) + if not self._re_log_raw: + self._re_log_raw = re.compile( + pattern=r"\s+message = \"(.*?)\",\n" # message + r"\s+kind = .OpenModelica.Scripting.ErrorKind.(.*?),\n" # kind + r"\s+level = .OpenModelica.Scripting.ErrorLevel.(.*?),\n" # level + r"\s+id = (.*?)" # id + "(,\n|\n)", # end marker + flags=re.MULTILINE | re.DOTALL) + + # extract all ErrorMessage records + log_entries = self._re_log_entries.findall(string=error_raw) + for log_entry in reversed(log_entries): + log_raw = self._re_log_raw.findall(string=log_entry) + if len(log_raw) != 1 or len(log_raw[0]) != 5: + logger.warning("Invalid ErrorMessage record returned by 'getMessagesStringInternal()':" + f" {repr(log_entry)}!") + + log_message = log_raw[0][0].encode().decode('unicode_escape') + log_kind = log_raw[0][1] + log_level = log_raw[0][2] + log_id = log_raw[0][3] + + msg = (f"[OMC log for 'sendExpression({command}, {parsed})']: " + f"[{log_kind}:{log_level}:{log_id}] {log_message}") + + # response according to the used log level + # see: https://build.openmodelica.org/Documentation/OpenModelica.Scripting.ErrorLevel.html + if log_level == 'error': + raise OMCSessionException(msg) + elif log_level == 'warning': + logger.warning(msg) + elif log_level == 'notification': + logger.info(msg) + else: # internal + logger.debug(msg) if parsed is True: try: From eae47bb7d2e3a54c0f638466bd82c4979bd2e7ef Mon Sep 17 00:00:00 2001 From: syntron Date: Thu, 8 May 2025 09:32:06 +0200 Subject: [PATCH 6/7] [OMCSessionZMQ] raise error if parsing of send Expression() result fails --- OMPython/OMCSession.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OMPython/OMCSession.py b/OMPython/OMCSession.py index 3795a8e4..2b2555f6 100644 --- a/OMPython/OMCSession.py +++ b/OMPython/OMCSession.py @@ -617,7 +617,6 @@ def sendExpression(self, command, parsed=True): try: return om_parser_basic(result) except (TypeError, UnboundLocalError) as ex: - logger.warning('OMParser error: %s. Returning the unparsed result.', ex) - return result + raise OMCSessionException("Cannot parse OMC result") from ex else: return result From 94ec48bd41771dde40e94e2ce9f56f47bdedbd73 Mon Sep 17 00:00:00 2001 From: syntron Date: Wed, 30 Apr 2025 13:38:13 +0200 Subject: [PATCH 7/7] [ModelicaSystem] do not print all output of OM command executed --- OMPython/ModelicaSystem.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OMPython/ModelicaSystem.py b/OMPython/ModelicaSystem.py index 7d65e393..4eb8adff 100644 --- a/OMPython/ModelicaSystem.py +++ b/OMPython/ModelicaSystem.py @@ -44,6 +44,7 @@ import importlib import pathlib from dataclasses import dataclass +import textwrap from typing import Optional from OMPython.OMCSession import OMCSessionZMQ, OMCSessionException @@ -337,7 +338,7 @@ def sendExpression(self, expr, parsed=True): except OMCSessionException as ex: raise ModelicaSystemError(f"Error executing {repr(expr)}") from ex - logger.debug(f"Result of executing {repr(expr)}: {repr(retval)}") + logger.debug(f"Result of executing {repr(expr)}: {textwrap.shorten(repr(retval), width=100)}") return retval