Skip to content

Commit

Permalink
Add API endpoints to access EMS global variables
Browse files Browse the repository at this point in the history
  • Loading branch information
Myoldmopar committed Jul 18, 2024
1 parent 15f6491 commit c1a2396
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 7 deletions.
44 changes: 44 additions & 0 deletions src/EnergyPlus/api/datatransfer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,50 @@ Real64 getInternalVariableValue(EnergyPlusState state, int handle)
return 0;
}

int getEMSGlobalVariableHandle(EnergyPlusState state, const char *name)
{
auto *thisState = static_cast<EnergyPlus::EnergyPlusData *>(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<EnergyPlus::EnergyPlusData *>(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<EnergyPlus::EnergyPlusData *>(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<EnergyPlus::EnergyPlusData *>(state);
Expand Down
36 changes: 35 additions & 1 deletion src/EnergyPlus/api/datatransfer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
88 changes: 82 additions & 6 deletions src/EnergyPlus/api/datatransfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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!
Expand Down Expand Up @@ -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
Expand Down

5 comments on commit c1a2396

@nrel-bot-2b
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACoupleAPIEndpoints (Myoldmopar) - x86_64-Linux-Ubuntu-22.04-gcc-11.4: OK (3685 of 3685 tests passed, 0 test warnings)

Build Badge Test Badge

@nrel-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACoupleAPIEndpoints (Myoldmopar) - Win64-Windows-10-VisualStudio-16: OK (2852 of 2852 tests passed, 0 test warnings)

Build Badge Test Badge

@nrel-bot-3
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACoupleAPIEndpoints (Myoldmopar) - x86_64-MacOS-10.18-clang-15.0.0: OK (3644 of 3644 tests passed, 0 test warnings)

Build Badge Test Badge

@nrel-bot-2c
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACoupleAPIEndpoints (Myoldmopar) - x86_64-Linux-Ubuntu-22.04-gcc-11.4-UnitTestsCoverage-Debug: OK (2060 of 2060 tests passed, 0 test warnings)

Build Badge Test Badge Coverage Badge

@nrel-bot-2
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACoupleAPIEndpoints (Myoldmopar) - x86_64-Linux-Ubuntu-22.04-gcc-11.4-IntegrationCoverage-Debug: OK (795 of 795 tests passed, 0 test warnings)

Build Badge Test Badge Coverage Badge

Please sign in to comment.