This is a Python based educational tool for studying fault equations defined by ASHRAE Guideline 36 for HVAC systems.
ASHRAE Guideline 36 provides uniform sequences of operation for HVAC systems that are intended to maximize the systems' energy efficiency and performance, provide control stability, and allow for real-time fault detection and diagnostics.
G36 for AHU's has 15 fault equations the first 13 of which are broken into separate .py
files. Fault equations 14 and
15
are omitted for the time being as these are for AHU systems with heating cooling coil leaving temperature sensors that
maybe not typical AHU type systems.
-
Git clone this repo
git clone https://github.com/RobertoChiosa/open-fdd.git
-
Each
fc.py
file contains aFaultCondition
and aFaultCodeReport
class. Run the.py
files in this fashion with specifying a data input argumenti
and an output argumento
which will be the name of the report Word document that can be retrieved from thefinal_report
directory after the script executes. Fault equation 6 is used as example on how to run a script:cd ./air_handling_unit python ./fc1.py -i ./ahu_data/MZVAV-1.csv -o example_report
The FaultCondition
class returns a new
Pandas dataframe with the fault flag as a new column. Some faults as defined by ASHRAE are only active in certain AHU
operating states like an AHU heating (OS #1), economizer (OS #2), economizer + mechanical cooling (OS #3), or a
mechanical cooling mode (OS #4). The operating states are defined as follows:
Operating State | Heating Valve Position | Cooling Valve Position | Outdoor Air Damper Position |
---|---|---|---|
#1: Heating | >0 | =0 | =min |
#2: Free cooling, modulating OA | =0 | =0 | min<x<100% |
#3: Mechanical + economizer cooling | =0 | =0 | =100% |
#4: Mechanical cooling, minimum OA | =0 | =0 | =min |
#5: Unknown or dehumidification | - | - | - |
This Python library (to be available on Pypi
in the future) internally handles to
ignore fault flags if the given fault flag is only to be active in a given AHU operating state (OS) or a combinations of
OS modes.
Under the hood of a FaultCondition
class a method (Python function inside a class) called apply
looks like this
below as an example shown for the fault condition 1 which returns the boolean flag as a Pandas dataframe
column (fc1_flag
) if the fault condition is present:
def apply(self, df: pd.DataFrame) -> pd.DataFrame:
df['static_check_'] = (
df[self.duct_static_col] < df[self.duct_static_setpoint_col] - self.duct_static_inches_err_thres)
df['fan_check_'] = (df[self.supply_vfd_speed_col] >=
self.vfd_speed_percent_max - self.vfd_speed_percent_err_thres)
df["fc1_flag"] = (df['static_check_'] & df['fan_check_']).astype(int)
if self.troubleshoot:
print("Troubleshoot mode enabled - not removing helper columns")
else:
del df['static_check_']
del df['fan_check_']
return df
The final report from passing data into the FaultCodeReport
class will output a Word document to a directory
containing the following info, currently tested on a months worth of data.
- a description of the fault equation
- a snip of the fault equation as defined by ASHRAE
- a plot of the data created with matplotlib with subplots
- data statistics to show the amount of time that the data contains as well as elapsed in hours and percent of time for
when the fault condition is
True
and elapsed time in hours for the fan motor runtime. - a histogram representing the hour of the day for when the fault equation is
True
. - sensor summary statistics filtered for when the AHU fan is running
⚠️ in the present moment is updating the repo to include °C and other metric system units currently only support imperial units but will incorporate this in future updates.
Required inputs in addition to a column name Date
with a Pandas readable time stamp tested in the format
of 12/22/2022 7:40:00 AM
:
Faults are summarized in the following table.
Fault Condition |
Python File |
Description | Applies to |
---|---|---|---|
1 | fc1.py | Supply fan not meeting duct static setpoint near 100% fan speed. | OS# 1 through OS# 5. |
2 | fc2.py | Mixing temp too high. | OS# 1 through OS# 5. |
3 | fc3.py | Mixing temp too high. | OS# 1 through OS# 5. |
4 | fc4.py | Control system excesses operating state.1 | OS# 1 through OS# 5. |
5 | fc5.py | Suppy air temp too low. | OS# 1. |
6 | fc6.py | OA fraction too high. | OS# 1 and OS# 4. |
7 | fc7.py | Supply air temp too low. | OS# 1. |
8 | fc8.py | Supply and mix air should be approx equal. | OS# 2. |
9 | fc9.py | Outside air temp too high for free cooling without additional mechanical cooling. | OS# 2. |
10 | fc10.py | Outside and mix air temp should be approx equal. | OS# 3. |
11 | fc11.py | Outside air temp too low for 100% OA cooling. | OS# 3. |
12 | fc12.py | Supply air too high; should be less than mix air temp. | OS# 3 and OS#4. |
13 | fc13.py | Supply air temp too high in full cooling. | OS# 3 and OS#4. |
The fault python files are structured as follows.
# parse command line parameters
args = custom_arg_parser()
# parameters settings
PARAM_1 = 0.05
PARAM_2 = 0.99
PARAM_3 = 0.1
[...]
# instantiate fault condition class
_fc = FaultConditionOne(
param_1=PARAM_1,
param_2=PARAM_2,
param_3=PARAM_3,
[...],
var_1="variable_name_1",
var_2="variable_name_2",
var_3="variable_name_3",
[...],
)
# instantiate fault code report class
_fc_report = FaultCodeOneReport(
param_1=PARAM_1,
param_2=PARAM_2,
param_3=PARAM_3,
[...],
var_1="variable_name_1",
var_2="variable_name_2",
var_3="variable_name_3",
[...],
)
# read in csv file
df = pd.read_csv(args.input, index_col="Date", parse_dates=True).rolling('5T').mean()
# describe dataset printing some stuff
describe_dataset(df)
# return a whole new dataframe with fault flag as new col
df_new = _fc.apply(df)
# save report
save_report(args, df, _fc_report)
The strings passed into the FaultCondition
class and FaultCodeReport
class represent the csv file column names and
required inputs for the given fault code.
- G36 does not mention anything about if the AHU is running or not. It could be wise to ignore any faults created when
the AHU is not running or fan status/command equals
False
or fan VFD speeds equal 0%. - G36 also expects data to be on one minute intervals and that a 5-minute rolling average be used in the analysis. The
rolling average is handled by the Pandas computing library when the data file in CSV format is read into memory:
df = pd.read_csv(args.input, index_col='Date', parse_dates=True).rolling('5T').mean()
More to come to incorporate G36 central cooling and heating plants (See PDF 2021 G36 that includes these equations in the PDF folder). Please submit a GitHub issue or start a GitHub conservation to request additional features. Pull requests encouraged to promote a community based free open source tool to help promote ASHRAE, HVAC optimization, and building carbon reduction efforts.
- Author Ben Bartling
- Contributor: Roberto Chiosa
MIT License Copyright 2022 Ben Bartling
Footnotes
-
The Pandas library computes AHU control system state changes per hour based on the data that is driving the AHU outputs, like heating/cooling valves and air damper analog commands. ↩