From c1a23960ed169e3191ed260194e6c6e15f21f7f1 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Thu, 18 Jul 2024 15:56:11 -0500 Subject: [PATCH] Add API endpoints to access EMS global variables --- src/EnergyPlus/api/datatransfer.cc | 44 +++++++++++++++ src/EnergyPlus/api/datatransfer.h | 36 +++++++++++- src/EnergyPlus/api/datatransfer.py | 88 ++++++++++++++++++++++++++++-- 3 files changed, 161 insertions(+), 7 deletions(-) diff --git a/src/EnergyPlus/api/datatransfer.cc b/src/EnergyPlus/api/datatransfer.cc index 196f1e2b406..fd8a303e07d 100644 --- a/src/EnergyPlus/api/datatransfer.cc +++ b/src/EnergyPlus/api/datatransfer.cc @@ -573,6 +573,50 @@ Real64 getInternalVariableValue(EnergyPlusState state, int handle) return 0; } +int getEMSGlobalVariableHandle(EnergyPlusState state, const char *name) +{ + auto *thisState = static_cast(state); + int index = 0; + for (auto const &erlVar : thisState->dataRuntimeLang->ErlVariable) { + index++; + if (EnergyPlus::Util::SameString(name, erlVar.Name)) { + return index; + } + } + return 0; +} + +Real64 getEMSGlobalVariableValue(EnergyPlusState state, int handle) +{ + auto *thisState = static_cast(state); + if (handle < 0 || handle > thisState->dataRuntimeLang->NumErlVariables) { + // need to fatal out once the process is done + // throw an error, set the fatal flag, and then return 0 + EnergyPlus::ShowSevereError( + *thisState, fmt::format("Data Exchange API: Problem -- index error in getEMSGlobalVariableValue; received handle: {}", handle)); + EnergyPlus::ShowContinueError( + *thisState, "The getEMSGlobalVariableValue function will return 0 for now to allow the process to finish, then EnergyPlus will abort"); + thisState->dataPluginManager->apiErrorFlag = true; + return 0; + } + return thisState->dataRuntimeLang->ErlVariable(handle).Value.Number; +} + +void setEMSGlobalVariableValue(EnergyPlusState state, int handle, Real64 value) +{ + auto *thisState = static_cast(state); + if (handle < 0 || handle > thisState->dataRuntimeLang->NumErlVariables) { + // need to fatal out once the plugin is done + // throw an error, set the fatal flag, and then return + EnergyPlus::ShowSevereError( + *thisState, fmt::format("Data Exchange API: Problem -- index error in setEMSGlobalVariableValue; received handle: {}", handle)); + EnergyPlus::ShowContinueError(*thisState, + "The setEMSGlobalVariableValue function will return to allow the plugin to finish, then EnergyPlus will abort"); + thisState->dataPluginManager->apiErrorFlag = true; + } + thisState->dataRuntimeLang->ErlVariable(handle).Value.Number = value; +} + int getPluginGlobalVariableHandle(EnergyPlusState state, const char *name) { auto *thisState = static_cast(state); diff --git a/src/EnergyPlus/api/datatransfer.h b/src/EnergyPlus/api/datatransfer.h index ae45b8fc5c8..11bee4ce75b 100644 --- a/src/EnergyPlus/api/datatransfer.h +++ b/src/EnergyPlus/api/datatransfer.h @@ -149,7 +149,7 @@ ENERGYPLUSLIB_API void freeAPIData(const struct APIDataEntry *data, unsigned int ENERGYPLUSLIB_API char **getObjectNames(EnergyPlusState state, const char *objectType, unsigned int *resultingSize); /// \brief Clears an object names array allocation /// \details This function frees an instance of the object names array, which is returned from getObjectNames -/// \param[in] data An array (pointer) of const char * as returned from the getObjectNames function +/// \param[in] objectNames An array (pointer) of char * as returned from the getObjectNames function /// \param[in] arraySize The size of the object name array, which is known after the call to getObjectNames. /// \return Nothing, this simply frees the memory ENERGYPLUSLIB_API void freeObjectNames(char **objectNames, unsigned int arraySize); @@ -297,6 +297,40 @@ ENERGYPLUSLIB_API int getInternalVariableHandle(EnergyPlusState state, const cha /// \see getInternalVariableHandle ENERGYPLUSLIB_API Real64 getInternalVariableValue(EnergyPlusState state, int handle); +// ----- FUNCTIONS RELATED TO EMS GLOBAL VARIABLES (FOR CORNER CASES WHERE PLUGIN/API BLENDS WITH EMS PROGRAMS) +/// \brief Gets a handle to an EMS "Global" variable +/// \details When using EMS, it is sometimes necessary to share data between programs. +/// EMS global variables are declared in the input file and used in EMS programs. +/// EMS global variables are identified by name only. This function returns -1 if a match is not found. +/// \param[in] state An active EnergyPlusState instance created with `stateNew`. +/// \param[in] name The name of the EMS global variable, which is declared in the EnergyPlus input file +/// \return The integer handle to an EMS global variable, or -1 if a match is not found. +/// \remark The behavior of this function is not well-defined until the `apiDataFullyReady` function returns true. +/// \see apiDataFullyReady +ENERGYPLUSLIB_API int getEMSGlobalVariableHandle(EnergyPlusState state, const char *name); +/// \brief Gets the current value of an EMS "Global" variable +/// \details When using EMS, the value of the shared "global" variables can change at any time. +/// This function returns the current value of the variable. +/// \param[in] state An active EnergyPlusState instance created with `stateNew`. +/// \param[in] handle The handle id to an EMS "Global" variable, which can be retrieved using the `getEMSGlobalVariableHandle` function. +/// \remark The behavior of this function is not well-defined until the `apiDataFullyReady` function returns true. +/// \return The current value of the variable, in floating point form, or zero if a handle problem is encountered. If a zero +/// is returned, use the `apiErrorFlag` function to disambiguate the return value. +/// \see apiDataFullyReady +/// \see getEMSGlobalVariableHandle +ENERGYPLUSLIB_API Real64 getEMSGlobalVariableValue(EnergyPlusState state, int handle); +/// \brief Sets the value of an EMS "Global" variable +/// \details When using EMS, the value of the shared "global" variables can change at any time. +/// This function sets the variable to a new value. +/// \param[in] state An active EnergyPlusState instance created with `stateNew`. +/// \param[in] handle The handle id to an EMS "Global" variable, which can be retrieved using the `getEMSGlobalVariableHandle` function. +/// \param[in] value The floating point value to be assigned to the global variable +/// \remark The behavior of this function is not well-defined until the `apiDataFullyReady` function returns true. +/// \remark A handle index or other problem will return 0 and set a flag to cause EnergyPlus to terminate once Python completes. +/// \see apiDataFullyReady +/// \see getEMSGlobalVariableHandle +ENERGYPLUSLIB_API void setEMSGlobalVariableValue(EnergyPlusState state, int handle, Real64 value); + // ----- FUNCTIONS RELATED TO PYTHON PLUGIN GLOBAL VARIABLES (ONLY USED FOR PYTHON PLUGIN SYSTEM) /// \brief Gets a handle to a Python Plugin "Global" variable diff --git a/src/EnergyPlus/api/datatransfer.py b/src/EnergyPlus/api/datatransfer.py index 3b154108d38..bee6023f1f9 100644 --- a/src/EnergyPlus/api/datatransfer.py +++ b/src/EnergyPlus/api/datatransfer.py @@ -201,6 +201,12 @@ def __init__(self, api: cdll, running_as_python_plugin: bool = False): self.api.currentEnvironmentNum.restype = c_int self.api.warmupFlag.argtypes = [c_void_p] self.api.warmupFlag.restype = c_int + self.api.getEMSGlobalVariableHandle.argtypes = [c_void_p, c_char_p] + self.api.getEMSGlobalVariableHandle.restype = c_int + self.api.getEMSGlobalVariableValue.argtypes = [c_void_p, c_int] + self.api.getEMSGlobalVariableValue.restype = RealEP + self.api.setEMSGlobalVariableValue.argtypes = [c_void_p, c_int, RealEP] + self.api.setEMSGlobalVariableValue.restype = c_void_p self.api.getPluginGlobalVariableHandle.argtypes = [c_void_p, c_char_p] self.api.getPluginGlobalVariableHandle.restype = c_int self.api.getPluginGlobalVariableValue.argtypes = [c_void_p, c_int] @@ -698,6 +704,82 @@ def get_construction_handle(self, state: c_void_p, var_name: Union[str, bytes]) "'{}'".format(var_name)) return self.api.getConstructionHandle(state, var_name) + def get_ems_global_handle(self, state: c_void_p, var_name: Union[str, bytes]) -> int: + """ + Get a handle to an EMS global variable in a running simulation. + + EMS global variables are used as a way to share data between running EMS programs. First a global variable must + be declared in the input file using the EnergyManagementSystem:GlobalVariable object. Once a name has been + declared, it can be accessed by EMS programs by name, and through the Python API. For API usage, the client + should get a handle to the variable using this get_global_handle function, then + using the get_ems_global_value and set_ems_global_value functions as needed. Note all global variables are + floating point values. + + The arguments passed into this function do not need to be a particular case, as the EnergyPlus API + automatically converts values to upper-case when finding matches to internal variables in the simulation. + + Note also that the arguments passed in here can be either strings or bytes, as this wrapper handles conversion + as needed. + + :param state: An active EnergyPlus "state" that is returned from a call to `api.state_manager.new_state()`. + :param var_name: The name of the EMS global variable to retrieve, this name must be listed in an IDF object: + `EnergyManagementSystem:GlobalVariable` + :return: An integer ID for this EMS global variable, or -1 if one could not be found. + """ + if isinstance(var_name, str): + var_name = var_name.encode('utf-8') + elif not isinstance(var_name, bytes): + raise EnergyPlusException( + "`get_ems_global_handle` expects `component_type` as a `str` or UTF-8 encoded `bytes`, not " + "'{}'".format(var_name)) + return self.api.getEMSGlobalVariableHandle(state, var_name) + + def get_ems_global_value(self, state: c_void_p, handle: int) -> float: + """ + Get the current value of an EMS global variable in a running simulation. + + EMS global variables are used as a way to share data between running EMS programs. First a global variable must + be declared in the input file using the EnergyManagementSystem:GlobalVariable object. Once a name has been + declared, it can be accessed by EMS programs by name, and through the Python API. For API usage, the client + should get a handle to the variable using this get_global_handle function, then + using the get_ems_global_value and set_ems_global_value functions as needed. Note all global variables are + floating point values. + + :param state: An active EnergyPlus "state" that is returned from a call to `api.state_manager.new_state()`. + :param handle: An integer returned from the `get_ems_global_handle` function. + :return: Floating point representation of the EMS global variable value + """ + if not is_number(handle): + raise EnergyPlusException( + "`get_ems_global_value` expects `handle` as an `int`, not " + "'{}'".format(handle)) + return self.api.getEMSGlobalVariableValue(state, handle) + + def set_ems_global_value(self, state: c_void_p, handle: int, value: float) -> None: + """ + Set the current value of an EMS global variable in a running simulation. + + EMS global variables are used as a way to share data between running EMS programs. First a global variable must + be declared in the input file using the EnergyManagementSystem:GlobalVariable object. Once a name has been + declared, it can be accessed by EMS programs by name, and through the Python API. For API usage, the client + should get a handle to the variable using this get_global_handle function, then + using the get_ems_global_value and set_ems_global_value functions as needed. Note all global variables are + floating point values. + + :param state: An active EnergyPlus "state" that is returned from a call to `api.state_manager.new_state()`. + :param handle: An integer returned from the `get_ems_global_handle` function. + :param value: Floating point value to assign to the EMS global variable + """ + if not is_number(handle): + raise EnergyPlusException( + "`set_ems_global_value` expects `variable_handle` as an `int`, not " + "'{}'".format(handle)) + if not is_number(value): + raise EnergyPlusException( + "`set_ems_global_value` expects `value` as a `float`, not " + "'{}'".format(value)) + self.api.setEMSGlobalVariableValue(state, handle, value) + def get_global_handle(self, state: c_void_p, var_name: Union[str, bytes]) -> int: """ Get a handle to a global variable in a running simulation. This is only used for Python Plugin applications! @@ -740,12 +822,6 @@ def get_global_value(self, state: c_void_p, handle: int) -> float: using this get_global_value and the set_global_value functions as needed. Note all global variables are floating point values. - The arguments passed into this function do not need to be a particular case, as the EnergyPlus API - automatically converts values to upper-case when finding matches to internal variables in the simulation. - - Note also that the arguments passed in here can be either strings or bytes, as this wrapper handles conversion - as needed. - :param state: An active EnergyPlus "state" that is returned from a call to `api.state_manager.new_state()`. :param handle: An integer returned from the `get_global_handle` function. :return: Floating point representation of the global variable value